mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge pull request #4187 from kittywhiskers/scantxout
merge #12196, #13697: Add scantxoutset RPC method
This commit is contained in:
commit
edf0552c0c
@ -239,6 +239,7 @@ BITCOIN_CORE_H = \
|
||||
rpc/util.h \
|
||||
saltedhasher.h \
|
||||
scheduler.h \
|
||||
script/descriptor.h \
|
||||
script/ismine.h \
|
||||
script/sigcache.h \
|
||||
script/sign.h \
|
||||
@ -576,6 +577,7 @@ libdash_common_a_SOURCES = \
|
||||
protocol.cpp \
|
||||
saltedhasher.cpp \
|
||||
scheduler.cpp \
|
||||
script/descriptor.cpp \
|
||||
script/ismine.cpp \
|
||||
script/sign.cpp \
|
||||
script/standard.cpp \
|
||||
|
@ -60,6 +60,7 @@ BITCOIN_TESTS =\
|
||||
test/cuckoocache_tests.cpp \
|
||||
test/denialofservice_tests.cpp \
|
||||
test/dip0020opcodes_tests.cpp \
|
||||
test/descriptor_tests.cpp \
|
||||
test/evo_deterministicmns_tests.cpp \
|
||||
test/evo_simplifiedmns_tests.cpp \
|
||||
test/fs_tests.cpp \
|
||||
|
@ -7,18 +7,22 @@
|
||||
#include <rpc/blockchain.h>
|
||||
|
||||
#include <amount.h>
|
||||
#include <base58.h>
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <checkpoints.h>
|
||||
#include <coins.h>
|
||||
#include <node/coinstats.h>
|
||||
#include <core_io.h>
|
||||
#include <consensus/validation.h>
|
||||
#include <key_io.h>
|
||||
#include <validation.h>
|
||||
#include <index/txindex.h>
|
||||
#include <policy/feerate.h>
|
||||
#include <policy/policy.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <rpc/server.h>
|
||||
#include <script/descriptor.h>
|
||||
#include <streams.h>
|
||||
#include <sync.h>
|
||||
#include <txdb.h>
|
||||
@ -36,6 +40,7 @@
|
||||
#include <llmq/quorums_chainlocks.h>
|
||||
#include <llmq/quorums_instantsend.h>
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <univalue.h>
|
||||
@ -2273,6 +2278,217 @@ static UniValue savemempool(const JSONRPCRequest& request)
|
||||
return NullUniValue;
|
||||
}
|
||||
|
||||
//! Search for a given set of pubkey scripts
|
||||
bool FindScriptPubKey(std::atomic<int>& scan_progress, const std::atomic<bool>& should_abort, int64_t& count, CCoinsViewCursor* cursor, const std::set<CScript>& needles, std::map<COutPoint, Coin>& out_results) {
|
||||
scan_progress = 0;
|
||||
count = 0;
|
||||
while (cursor->Valid()) {
|
||||
COutPoint key;
|
||||
Coin coin;
|
||||
if (!cursor->GetKey(key) || !cursor->GetValue(coin)) return false;
|
||||
if (++count % 8192 == 0) {
|
||||
boost::this_thread::interruption_point();
|
||||
if (should_abort) {
|
||||
// allow to abort the scan via the abort reference
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (count % 256 == 0) {
|
||||
// update progress reference every 256 item
|
||||
uint32_t high = 0x100 * *key.hash.begin() + *(key.hash.begin() + 1);
|
||||
scan_progress = (int)(high * 100.0 / 65536.0 + 0.5);
|
||||
}
|
||||
if (needles.count(coin.out.scriptPubKey)) {
|
||||
out_results.emplace(key, coin);
|
||||
}
|
||||
cursor->Next();
|
||||
}
|
||||
scan_progress = 100;
|
||||
return true;
|
||||
}
|
||||
|
||||
/** RAII object to prevent concurrency issue when scanning the txout set */
|
||||
static std::mutex g_utxosetscan;
|
||||
static std::atomic<int> g_scan_progress;
|
||||
static std::atomic<bool> g_scan_in_progress;
|
||||
static std::atomic<bool> g_should_abort_scan;
|
||||
class CoinsViewScanReserver
|
||||
{
|
||||
private:
|
||||
bool m_could_reserve;
|
||||
public:
|
||||
explicit CoinsViewScanReserver() : m_could_reserve(false) {}
|
||||
|
||||
bool reserve() {
|
||||
assert (!m_could_reserve);
|
||||
std::lock_guard<std::mutex> lock(g_utxosetscan);
|
||||
if (g_scan_in_progress) {
|
||||
return false;
|
||||
}
|
||||
g_scan_in_progress = true;
|
||||
m_could_reserve = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
~CoinsViewScanReserver() {
|
||||
if (m_could_reserve) {
|
||||
std::lock_guard<std::mutex> lock(g_utxosetscan);
|
||||
g_scan_in_progress = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UniValue scantxoutset(const JSONRPCRequest& request)
|
||||
{
|
||||
if (request.fHelp || request.params.size() < 1 || request.params.size() > 2)
|
||||
throw std::runtime_error(
|
||||
"scantxoutset <action> ( <scanobjects> )\n"
|
||||
"\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n"
|
||||
"\nScans the unspent transaction output set for entries that match certain output descriptors.\n"
|
||||
"Examples of output descriptors are:\n"
|
||||
" addr(<address>) Outputs whose scriptPubKey corresponds to the specified address (does not include P2PK)\n"
|
||||
" raw(<hex script>) Outputs whose scriptPubKey equals the specified hex scripts\n"
|
||||
" combo(<pubkey>) P2PK and P2PKH outputs for the given pubkey\n"
|
||||
" pkh(<pubkey>) P2PKH outputs for the given pubkey\n"
|
||||
" sh(multi(<n>,<pubkey>,<pubkey>,...)) P2SH-multisig outputs for the given threshold and pubkeys\n"
|
||||
"\nIn the above, <pubkey> either refers to a fixed public key in hexadecimal notation, or to an xpub/xprv optionally followed by one\n"
|
||||
"or more path elements separated by \"/\", and optionally ending in \"/*\" (unhardened), or \"/*'\" or \"/*h\" (hardened) to specify all\n"
|
||||
"unhardened or hardened child keys.\n"
|
||||
"In the latter case, a range needs to be specified by below if different from 1000.\n"
|
||||
"For more information on output descriptors, see the documentation at TODO\n"
|
||||
"\nArguments:\n"
|
||||
"1. \"action\" (string, required) The action to execute\n"
|
||||
" \"start\" for starting a scan\n"
|
||||
" \"abort\" for aborting the current scan (returns true when abort was successful)\n"
|
||||
" \"status\" for progress report (in %) of the current scan\n"
|
||||
"2. \"scanobjects\" (array, required) Array of scan objects\n"
|
||||
" [ Every scan object is either a string descriptor or an object:\n"
|
||||
" \"descriptor\", (string, optional) An output descriptor\n"
|
||||
" { (object, optional) An object with output descriptor and metadata\n"
|
||||
" \"desc\": \"descriptor\", (string, required) An output descriptor\n"
|
||||
" \"range\": n, (numeric, optional) Up to what child index HD chains should be explored (default: 1000)\n"
|
||||
" },\n"
|
||||
" ...\n"
|
||||
" ]\n"
|
||||
"\nResult:\n"
|
||||
"{\n"
|
||||
" \"unspents\": [\n"
|
||||
" {\n"
|
||||
" \"txid\" : \"transactionid\", (string) The transaction id\n"
|
||||
" \"vout\": n, (numeric) the vout value\n"
|
||||
" \"scriptPubKey\" : \"script\", (string) the script key\n"
|
||||
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " of the unspent output\n"
|
||||
" \"height\" : n, (numeric) Height of the unspent transaction output\n"
|
||||
" }\n"
|
||||
" ,...], \n"
|
||||
" \"total_amount\" : x.xxx, (numeric) The total amount of all found unspent outputs in " + CURRENCY_UNIT + "\n"
|
||||
"]\n"
|
||||
);
|
||||
|
||||
RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR});
|
||||
|
||||
UniValue result(UniValue::VOBJ);
|
||||
if (request.params[0].get_str() == "status") {
|
||||
CoinsViewScanReserver reserver;
|
||||
if (reserver.reserve()) {
|
||||
// no scan in progress
|
||||
return NullUniValue;
|
||||
}
|
||||
result.pushKV("progress", g_scan_progress);
|
||||
return result;
|
||||
} else if (request.params[0].get_str() == "abort") {
|
||||
CoinsViewScanReserver reserver;
|
||||
if (reserver.reserve()) {
|
||||
// reserve was possible which means no scan was running
|
||||
return false;
|
||||
}
|
||||
// set the abort flag
|
||||
g_should_abort_scan = true;
|
||||
return true;
|
||||
} else if (request.params[0].get_str() == "start") {
|
||||
CoinsViewScanReserver reserver;
|
||||
if (!reserver.reserve()) {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan already in progress, use action \"abort\" or \"status\"");
|
||||
}
|
||||
std::set<CScript> needles;
|
||||
CAmount total_in = 0;
|
||||
|
||||
// loop through the scan objects
|
||||
for (const UniValue& scanobject : request.params[1].get_array().getValues()) {
|
||||
std::string desc_str;
|
||||
int range = 1000;
|
||||
if (scanobject.isStr()) {
|
||||
desc_str = scanobject.get_str();
|
||||
} else if (scanobject.isObject()) {
|
||||
UniValue desc_uni = find_value(scanobject, "desc");
|
||||
if (desc_uni.isNull()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor needs to be provided in scan object");
|
||||
desc_str = desc_uni.get_str();
|
||||
UniValue range_uni = find_value(scanobject, "range");
|
||||
if (!range_uni.isNull()) {
|
||||
range = range_uni.get_int();
|
||||
if (range < 0 || range > 1000000) throw JSONRPCError(RPC_INVALID_PARAMETER, "range out of range");
|
||||
}
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Scan object needs to be either a string or an object");
|
||||
}
|
||||
|
||||
FlatSigningProvider provider;
|
||||
auto desc = Parse(desc_str, provider);
|
||||
if (!desc) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Invalid descriptor '%s'", desc_str));
|
||||
}
|
||||
if (!desc->IsRange()) range = 0;
|
||||
for (int i = 0; i <= range; ++i) {
|
||||
std::vector<CScript> scripts;
|
||||
if (!desc->Expand(i, provider, scripts, provider)) {
|
||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Cannot derive script without private keys: '%s'", desc_str));
|
||||
}
|
||||
needles.insert(scripts.begin(), scripts.end());
|
||||
}
|
||||
}
|
||||
|
||||
// Scan the unspent transaction output set for inputs
|
||||
UniValue unspents(UniValue::VARR);
|
||||
std::vector<CTxOut> input_txos;
|
||||
std::map<COutPoint, Coin> coins;
|
||||
g_should_abort_scan = false;
|
||||
g_scan_progress = 0;
|
||||
int64_t count = 0;
|
||||
std::unique_ptr<CCoinsViewCursor> pcursor;
|
||||
{
|
||||
LOCK(cs_main);
|
||||
FlushStateToDisk();
|
||||
pcursor = std::unique_ptr<CCoinsViewCursor>(pcoinsdbview->Cursor());
|
||||
assert(pcursor);
|
||||
}
|
||||
bool res = FindScriptPubKey(g_scan_progress, g_should_abort_scan, count, pcursor.get(), needles, coins);
|
||||
result.pushKV("success", res);
|
||||
result.pushKV("searched_items", count);
|
||||
|
||||
for (const auto& it : coins) {
|
||||
const COutPoint& outpoint = it.first;
|
||||
const Coin& coin = it.second;
|
||||
const CTxOut& txo = coin.out;
|
||||
input_txos.push_back(txo);
|
||||
total_in += txo.nValue;
|
||||
|
||||
UniValue unspent(UniValue::VOBJ);
|
||||
unspent.pushKV("txid", outpoint.hash.GetHex());
|
||||
unspent.pushKV("vout", (int32_t)outpoint.n);
|
||||
unspent.pushKV("scriptPubKey", HexStr(txo.scriptPubKey));
|
||||
unspent.pushKV("amount", ValueFromAmount(txo.nValue));
|
||||
unspent.pushKV("height", (int32_t)coin.nHeight);
|
||||
|
||||
unspents.push_back(unspent);
|
||||
}
|
||||
result.pushKV("unspents", unspents);
|
||||
result.pushKV("total_amount", ValueFromAmount(total_in));
|
||||
} else {
|
||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static const CRPCCommand commands[] =
|
||||
{ // category name actor (function) argNames
|
||||
// --------------------- ------------------------ ----------------------- ----------
|
||||
@ -2303,6 +2519,7 @@ static const CRPCCommand commands[] =
|
||||
{ "blockchain", "verifychain", &verifychain, {"checklevel","nblocks"} },
|
||||
|
||||
{ "blockchain", "preciousblock", &preciousblock, {"blockhash"} },
|
||||
{ "blockchain", "scantxoutset", &scantxoutset, {"action", "scanobjects"} },
|
||||
|
||||
/* Not shown in help */
|
||||
{ "hidden", "invalidateblock", &invalidateblock, {"blockhash"} },
|
||||
|
@ -97,6 +97,7 @@ static const CRPCConvertParam vRPCConvertParams[] =
|
||||
{ "sendmany", 6, "use_is" },
|
||||
{ "sendmany", 7, "use_cj" },
|
||||
{ "sendmany", 8, "conf_target" },
|
||||
{ "scantxoutset", 1, "scanobjects" },
|
||||
{ "addmultisigaddress", 0, "nrequired" },
|
||||
{ "addmultisigaddress", 1, "keys" },
|
||||
{ "createmultisig", 0, "nrequired" },
|
||||
|
547
src/script/descriptor.cpp
Normal file
547
src/script/descriptor.cpp
Normal file
@ -0,0 +1,547 @@
|
||||
// Copyright (c) 2018 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <script/descriptor.h>
|
||||
|
||||
#include <key_io.h>
|
||||
#include <pubkey.h>
|
||||
#include <script/script.h>
|
||||
#include <script/standard.h>
|
||||
|
||||
#include <span.h>
|
||||
#include <util/system.h>
|
||||
#include <util/memory.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Internal representation //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
typedef std::vector<uint32_t> KeyPath;
|
||||
|
||||
std::string FormatKeyPath(const KeyPath& path)
|
||||
{
|
||||
std::string ret;
|
||||
for (auto i : path) {
|
||||
ret += strprintf("/%i", (i << 1) >> 1);
|
||||
if (i >> 31) ret += '\'';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Interface for public key objects in descriptors. */
|
||||
struct PubkeyProvider
|
||||
{
|
||||
virtual ~PubkeyProvider() = default;
|
||||
|
||||
/** Derive a public key. */
|
||||
virtual bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const = 0;
|
||||
|
||||
/** Whether this represent multiple public keys at different positions. */
|
||||
virtual bool IsRange() const = 0;
|
||||
|
||||
/** Get the size of the generated public key(s) in bytes (33 or 65). */
|
||||
virtual size_t GetSize() const = 0;
|
||||
|
||||
/** Get the descriptor string form. */
|
||||
virtual std::string ToString() const = 0;
|
||||
|
||||
/** Get the descriptor string form including private data (if available in arg). */
|
||||
virtual bool ToPrivateString(const SigningProvider& arg, std::string& out) const = 0;
|
||||
};
|
||||
|
||||
/** An object representing a parsed constant public key in a descriptor. */
|
||||
class ConstPubkeyProvider final : public PubkeyProvider
|
||||
{
|
||||
CPubKey m_pubkey;
|
||||
|
||||
public:
|
||||
ConstPubkeyProvider(const CPubKey& pubkey) : m_pubkey(pubkey) {}
|
||||
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override
|
||||
{
|
||||
out = m_pubkey;
|
||||
return true;
|
||||
}
|
||||
bool IsRange() const override { return false; }
|
||||
size_t GetSize() const override { return m_pubkey.size(); }
|
||||
std::string ToString() const override { return HexStr(m_pubkey); }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& ret) const override
|
||||
{
|
||||
CKey key;
|
||||
if (!arg.GetKey(m_pubkey.GetID(), key)) return false;
|
||||
ret = EncodeSecret(key);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
enum class DeriveType {
|
||||
NO,
|
||||
UNHARDENED,
|
||||
HARDENED,
|
||||
};
|
||||
|
||||
/** An object representing a parsed extended public key in a descriptor. */
|
||||
class BIP32PubkeyProvider final : public PubkeyProvider
|
||||
{
|
||||
CExtPubKey m_extkey;
|
||||
KeyPath m_path;
|
||||
DeriveType m_derive;
|
||||
|
||||
bool GetExtKey(const SigningProvider& arg, CExtKey& ret) const
|
||||
{
|
||||
CKey key;
|
||||
if (!arg.GetKey(m_extkey.pubkey.GetID(), key)) return false;
|
||||
ret.nDepth = m_extkey.nDepth;
|
||||
std::copy(m_extkey.vchFingerprint, m_extkey.vchFingerprint + 4, ret.vchFingerprint);
|
||||
ret.nChild = m_extkey.nChild;
|
||||
ret.chaincode = m_extkey.chaincode;
|
||||
ret.key = key;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsHardened() const
|
||||
{
|
||||
if (m_derive == DeriveType::HARDENED) return true;
|
||||
for (auto entry : m_path) {
|
||||
if (entry >> 31) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
BIP32PubkeyProvider(const CExtPubKey& extkey, KeyPath path, DeriveType derive) : m_extkey(extkey), m_path(std::move(path)), m_derive(derive) {}
|
||||
bool IsRange() const override { return m_derive != DeriveType::NO; }
|
||||
size_t GetSize() const override { return 33; }
|
||||
bool GetPubKey(int pos, const SigningProvider& arg, CPubKey& out) const override
|
||||
{
|
||||
if (IsHardened()) {
|
||||
CExtKey key;
|
||||
if (!GetExtKey(arg, key)) return false;
|
||||
for (auto entry : m_path) {
|
||||
key.Derive(key, entry);
|
||||
}
|
||||
if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos);
|
||||
if (m_derive == DeriveType::HARDENED) key.Derive(key, pos | 0x80000000UL);
|
||||
out = key.Neuter().pubkey;
|
||||
} else {
|
||||
// TODO: optimize by caching
|
||||
CExtPubKey key = m_extkey;
|
||||
for (auto entry : m_path) {
|
||||
key.Derive(key, entry);
|
||||
}
|
||||
if (m_derive == DeriveType::UNHARDENED) key.Derive(key, pos);
|
||||
assert(m_derive != DeriveType::HARDENED);
|
||||
out = key.pubkey;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
std::string ToString() const override
|
||||
{
|
||||
std::string ret = EncodeExtPubKey(m_extkey) + FormatKeyPath(m_path);
|
||||
if (IsRange()) {
|
||||
ret += "/*";
|
||||
if (m_derive == DeriveType::HARDENED) ret += '\'';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
|
||||
{
|
||||
CExtKey key;
|
||||
if (!GetExtKey(arg, key)) return false;
|
||||
out = EncodeExtKey(key) + FormatKeyPath(m_path);
|
||||
if (IsRange()) {
|
||||
out += "/*";
|
||||
if (m_derive == DeriveType::HARDENED) out += '\'';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** A parsed addr(A) descriptor. */
|
||||
class AddressDescriptor final : public Descriptor
|
||||
{
|
||||
CTxDestination m_destination;
|
||||
|
||||
public:
|
||||
AddressDescriptor(CTxDestination destination) : m_destination(std::move(destination)) {}
|
||||
|
||||
bool IsRange() const override { return false; }
|
||||
std::string ToString() const override { return "addr(" + EncodeDestination(m_destination) + ")"; }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; }
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
output_scripts = std::vector<CScript>{GetScriptForDestination(m_destination)};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** A parsed raw(H) descriptor. */
|
||||
class RawDescriptor final : public Descriptor
|
||||
{
|
||||
CScript m_script;
|
||||
|
||||
public:
|
||||
RawDescriptor(CScript script) : m_script(std::move(script)) {}
|
||||
|
||||
bool IsRange() const override { return false; }
|
||||
std::string ToString() const override { return "raw(" + HexStr(m_script) + ")"; }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override { out = ToString(); return true; }
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
output_scripts = std::vector<CScript>{m_script};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** A parsed pk(P), pkh(P) descriptor. */
|
||||
class SingleKeyDescriptor final : public Descriptor
|
||||
{
|
||||
const std::function<CScript(const CPubKey&)> m_script_fn;
|
||||
const std::string m_fn_name;
|
||||
std::unique_ptr<PubkeyProvider> m_provider;
|
||||
|
||||
public:
|
||||
SingleKeyDescriptor(std::unique_ptr<PubkeyProvider> prov, const std::function<CScript(const CPubKey&)>& fn, const std::string& name) : m_script_fn(fn), m_fn_name(name), m_provider(std::move(prov)) {}
|
||||
|
||||
bool IsRange() const override { return m_provider->IsRange(); }
|
||||
std::string ToString() const override { return m_fn_name + "(" + m_provider->ToString() + ")"; }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
|
||||
{
|
||||
std::string ret;
|
||||
if (!m_provider->ToPrivateString(arg, ret)) return false;
|
||||
out = m_fn_name + "(" + std::move(ret) + ")";
|
||||
return true;
|
||||
}
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
CPubKey key;
|
||||
if (!m_provider->GetPubKey(pos, arg, key)) return false;
|
||||
output_scripts = std::vector<CScript>{m_script_fn(key)};
|
||||
out.pubkeys.emplace(key.GetID(), std::move(key));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
CScript P2PKHGetScript(const CPubKey& pubkey) { return GetScriptForDestination(pubkey.GetID()); }
|
||||
CScript P2PKGetScript(const CPubKey& pubkey) { return GetScriptForRawPubKey(pubkey); }
|
||||
|
||||
|
||||
/** A parsed multi(...) descriptor. */
|
||||
class MultisigDescriptor : public Descriptor
|
||||
{
|
||||
int m_threshold;
|
||||
std::vector<std::unique_ptr<PubkeyProvider>> m_providers;
|
||||
|
||||
public:
|
||||
MultisigDescriptor(int threshold, std::vector<std::unique_ptr<PubkeyProvider>> providers) : m_threshold(threshold), m_providers(std::move(providers)) {}
|
||||
|
||||
bool IsRange() const override
|
||||
{
|
||||
for (const auto& p : m_providers) {
|
||||
if (p->IsRange()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string ToString() const override
|
||||
{
|
||||
std::string ret = strprintf("multi(%i", m_threshold);
|
||||
for (const auto& p : m_providers) {
|
||||
ret += "," + p->ToString();
|
||||
}
|
||||
return std::move(ret) + ")";
|
||||
}
|
||||
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
|
||||
{
|
||||
std::string ret = strprintf("multi(%i", m_threshold);
|
||||
for (const auto& p : m_providers) {
|
||||
std::string sub;
|
||||
if (!p->ToPrivateString(arg, sub)) return false;
|
||||
ret += "," + std::move(sub);
|
||||
}
|
||||
out = std::move(ret) + ")";
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
std::vector<CPubKey> pubkeys;
|
||||
pubkeys.reserve(m_providers.size());
|
||||
for (const auto& p : m_providers) {
|
||||
CPubKey key;
|
||||
if (!p->GetPubKey(pos, arg, key)) return false;
|
||||
pubkeys.push_back(key);
|
||||
}
|
||||
for (const CPubKey& key : pubkeys) {
|
||||
out.pubkeys.emplace(key.GetID(), std::move(key));
|
||||
}
|
||||
output_scripts = std::vector<CScript>{GetScriptForMultisig(m_threshold, pubkeys)};
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/** A parsed sh(S) descriptor. */
|
||||
class ConvertorDescriptor : public Descriptor
|
||||
{
|
||||
const std::function<CScript(const CScript&)> m_convert_fn;
|
||||
const std::string m_fn_name;
|
||||
std::unique_ptr<Descriptor> m_descriptor;
|
||||
|
||||
public:
|
||||
ConvertorDescriptor(std::unique_ptr<Descriptor> descriptor, const std::function<CScript(const CScript&)>& fn, const std::string& name) : m_convert_fn(fn), m_fn_name(name), m_descriptor(std::move(descriptor)) {}
|
||||
|
||||
bool IsRange() const override { return m_descriptor->IsRange(); }
|
||||
std::string ToString() const override { return m_fn_name + "(" + m_descriptor->ToString() + ")"; }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
|
||||
{
|
||||
std::string ret;
|
||||
if (!m_descriptor->ToPrivateString(arg, ret)) return false;
|
||||
out = m_fn_name + "(" + std::move(ret) + ")";
|
||||
return true;
|
||||
}
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
std::vector<CScript> sub;
|
||||
if (!m_descriptor->Expand(pos, arg, sub, out)) return false;
|
||||
output_scripts.clear();
|
||||
for (const auto& script : sub) {
|
||||
CScriptID id(script);
|
||||
out.scripts.emplace(CScriptID(script), script);
|
||||
output_scripts.push_back(m_convert_fn(script));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
CScript ConvertP2SH(const CScript& script) { return GetScriptForDestination(CScriptID(script)); }
|
||||
|
||||
/** A parsed combo(P) descriptor. */
|
||||
class ComboDescriptor final : public Descriptor
|
||||
{
|
||||
std::unique_ptr<PubkeyProvider> m_provider;
|
||||
|
||||
public:
|
||||
ComboDescriptor(std::unique_ptr<PubkeyProvider> provider) : m_provider(std::move(provider)) {}
|
||||
|
||||
bool IsRange() const override { return m_provider->IsRange(); }
|
||||
std::string ToString() const override { return "combo(" + m_provider->ToString() + ")"; }
|
||||
bool ToPrivateString(const SigningProvider& arg, std::string& out) const override
|
||||
{
|
||||
std::string ret;
|
||||
if (!m_provider->ToPrivateString(arg, ret)) return false;
|
||||
out = "combo(" + std::move(ret) + ")";
|
||||
return true;
|
||||
}
|
||||
bool Expand(int pos, const SigningProvider& arg, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const override
|
||||
{
|
||||
CPubKey key;
|
||||
if (!m_provider->GetPubKey(pos, arg, key)) return false;
|
||||
CKeyID keyid = key.GetID();
|
||||
{
|
||||
CScript p2pk = GetScriptForRawPubKey(key);
|
||||
CScript p2pkh = GetScriptForDestination(keyid);
|
||||
output_scripts = std::vector<CScript>{std::move(p2pk), std::move(p2pkh)};
|
||||
out.pubkeys.emplace(keyid, key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Parser //
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
enum class ParseScriptContext {
|
||||
TOP,
|
||||
P2SH
|
||||
};
|
||||
|
||||
/** Parse a constant. If succesful, sp is updated to skip the constant and return true. */
|
||||
bool Const(const std::string& str, Span<const char>& sp)
|
||||
{
|
||||
if ((size_t)sp.size() >= str.size() && std::equal(str.begin(), str.end(), sp.begin())) {
|
||||
sp = sp.subspan(str.size());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Parse a function call. If succesful, sp is updated to be the function's argument(s). */
|
||||
bool Func(const std::string& str, Span<const char>& sp)
|
||||
{
|
||||
if ((size_t)sp.size() >= str.size() + 2 && sp[str.size()] == '(' && sp[sp.size() - 1] == ')' && std::equal(str.begin(), str.end(), sp.begin())) {
|
||||
sp = sp.subspan(str.size() + 1, sp.size() - str.size() - 2);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Return the expression that sp begins with, and update sp to skip it. */
|
||||
Span<const char> Expr(Span<const char>& sp)
|
||||
{
|
||||
int level = 0;
|
||||
auto it = sp.begin();
|
||||
while (it != sp.end()) {
|
||||
if (*it == '(') {
|
||||
++level;
|
||||
} else if (level && *it == ')') {
|
||||
--level;
|
||||
} else if (level == 0 && (*it == ')' || *it == ',')) {
|
||||
break;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
Span<const char> ret = sp.first(it - sp.begin());
|
||||
sp = sp.subspan(it - sp.begin());
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Split a string on every instance of sep, returning a vector. */
|
||||
std::vector<Span<const char>> Split(const Span<const char>& sp, char sep)
|
||||
{
|
||||
std::vector<Span<const char>> ret;
|
||||
auto it = sp.begin();
|
||||
auto start = it;
|
||||
while (it != sp.end()) {
|
||||
if (*it == sep) {
|
||||
ret.emplace_back(start, it);
|
||||
start = it + 1;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
ret.emplace_back(start, it);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/** Parse a key path, being passed a split list of elements (the first element is ignored). */
|
||||
bool ParseKeyPath(const std::vector<Span<const char>>& split, KeyPath& out)
|
||||
{
|
||||
for (size_t i = 1; i < split.size(); ++i) {
|
||||
Span<const char> elem = split[i];
|
||||
bool hardened = false;
|
||||
if (elem.size() > 0 && (elem[elem.size() - 1] == '\'' || elem[elem.size() - 1] == 'h')) {
|
||||
elem = elem.first(elem.size() - 1);
|
||||
hardened = true;
|
||||
}
|
||||
uint32_t p;
|
||||
if (!ParseUInt32(std::string(elem.begin(), elem.end()), &p) || p > 0x7FFFFFFFUL) return false;
|
||||
out.push_back(p | (((uint32_t)hardened) << 31));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<PubkeyProvider> ParsePubkey(const Span<const char>& sp, bool permit_uncompressed, FlatSigningProvider& out)
|
||||
{
|
||||
auto split = Split(sp, '/');
|
||||
std::string str(split[0].begin(), split[0].end());
|
||||
if (split.size() == 1) {
|
||||
if (IsHex(str)) {
|
||||
std::vector<unsigned char> data = ParseHex(str);
|
||||
CPubKey pubkey(data);
|
||||
if (pubkey.IsFullyValid() && (permit_uncompressed || pubkey.IsCompressed())) return MakeUnique<ConstPubkeyProvider>(pubkey);
|
||||
}
|
||||
CKey key = DecodeSecret(str);
|
||||
if (key.IsValid() && (permit_uncompressed || key.IsCompressed())) {
|
||||
CPubKey pubkey = key.GetPubKey();
|
||||
out.keys.emplace(pubkey.GetID(), key);
|
||||
return MakeUnique<ConstPubkeyProvider>(pubkey);
|
||||
}
|
||||
}
|
||||
CExtKey extkey = DecodeExtKey(str);
|
||||
CExtPubKey extpubkey = DecodeExtPubKey(str);
|
||||
if (!extkey.key.IsValid() && !extpubkey.pubkey.IsValid()) return nullptr;
|
||||
KeyPath path;
|
||||
DeriveType type = DeriveType::NO;
|
||||
if (split.back() == MakeSpan("*").first(1)) {
|
||||
split.pop_back();
|
||||
type = DeriveType::UNHARDENED;
|
||||
} else if (split.back() == MakeSpan("*'").first(2) || split.back() == MakeSpan("*h").first(2)) {
|
||||
split.pop_back();
|
||||
type = DeriveType::HARDENED;
|
||||
}
|
||||
if (!ParseKeyPath(split, path)) return nullptr;
|
||||
if (extkey.key.IsValid()) {
|
||||
extpubkey = extkey.Neuter();
|
||||
out.keys.emplace(extpubkey.pubkey.GetID(), extkey.key);
|
||||
}
|
||||
return MakeUnique<BIP32PubkeyProvider>(extpubkey, std::move(path), type);
|
||||
}
|
||||
|
||||
/** Parse a script in a particular context. */
|
||||
std::unique_ptr<Descriptor> ParseScript(Span<const char>& sp, ParseScriptContext ctx, FlatSigningProvider& out)
|
||||
{
|
||||
auto expr = Expr(sp);
|
||||
if (Func("pk", expr)) {
|
||||
auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2SH, out);
|
||||
if (!pubkey) return nullptr;
|
||||
return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKGetScript, "pk");
|
||||
}
|
||||
if (Func("pkh", expr)) {
|
||||
auto pubkey = ParsePubkey(expr, ctx != ParseScriptContext::P2SH, out);
|
||||
if (!pubkey) return nullptr;
|
||||
return MakeUnique<SingleKeyDescriptor>(std::move(pubkey), P2PKHGetScript, "pkh");
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("combo", expr)) {
|
||||
auto pubkey = ParsePubkey(expr, true, out);
|
||||
if (!pubkey) return nullptr;
|
||||
return MakeUnique<ComboDescriptor>(std::move(pubkey));
|
||||
}
|
||||
if (Func("multi", expr)) {
|
||||
auto threshold = Expr(expr);
|
||||
uint32_t thres;
|
||||
std::vector<std::unique_ptr<PubkeyProvider>> providers;
|
||||
if (!ParseUInt32(std::string(threshold.begin(), threshold.end()), &thres)) return nullptr;
|
||||
size_t script_size = 0;
|
||||
while (expr.size()) {
|
||||
if (!Const(",", expr)) return nullptr;
|
||||
auto arg = Expr(expr);
|
||||
auto pk = ParsePubkey(arg, ctx != ParseScriptContext::P2SH, out);
|
||||
if (!pk) return nullptr;
|
||||
script_size += pk->GetSize() + 1;
|
||||
providers.emplace_back(std::move(pk));
|
||||
}
|
||||
if (providers.size() < 1 || providers.size() > 16 || thres < 1 || thres > providers.size()) return nullptr;
|
||||
if (ctx == ParseScriptContext::TOP) {
|
||||
if (providers.size() > 3) return nullptr; // Not more than 3 pubkeys for raw multisig
|
||||
}
|
||||
if (ctx == ParseScriptContext::P2SH) {
|
||||
if (script_size + 3 > 520) return nullptr; // Enforce P2SH script size limit
|
||||
}
|
||||
return MakeUnique<MultisigDescriptor>(thres, std::move(providers));
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("sh", expr)) {
|
||||
auto desc = ParseScript(expr, ParseScriptContext::P2SH, out);
|
||||
if (!desc || expr.size()) return nullptr;
|
||||
return MakeUnique<ConvertorDescriptor>(std::move(desc), ConvertP2SH, "sh");
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("addr", expr)) {
|
||||
CTxDestination dest = DecodeDestination(std::string(expr.begin(), expr.end()));
|
||||
if (!IsValidDestination(dest)) return nullptr;
|
||||
return MakeUnique<AddressDescriptor>(std::move(dest));
|
||||
}
|
||||
if (ctx == ParseScriptContext::TOP && Func("raw", expr)) {
|
||||
std::string str(expr.begin(), expr.end());
|
||||
if (!IsHex(str)) return nullptr;
|
||||
auto bytes = ParseHex(str);
|
||||
return MakeUnique<RawDescriptor>(CScript(bytes.begin(), bytes.end()));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out)
|
||||
{
|
||||
Span<const char> sp(descriptor.data(), descriptor.size());
|
||||
auto ret = ParseScript(sp, ParseScriptContext::TOP, out);
|
||||
if (sp.size() == 0 && ret) return ret;
|
||||
return nullptr;
|
||||
}
|
99
src/script/descriptor.h
Normal file
99
src/script/descriptor.h
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright (c) 2018 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#ifndef BITCOIN_SCRIPT_DESCRIPTOR_H
|
||||
#define BITCOIN_SCRIPT_DESCRIPTOR_H
|
||||
|
||||
#include <script/script.h>
|
||||
#include <script/sign.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
// Descriptors are strings that describe a set of scriptPubKeys, together with
|
||||
// all information necessary to solve them. By combining all information into
|
||||
// one, they avoid the need to separately import keys and scripts.
|
||||
//
|
||||
// Descriptors may be ranged, which occurs when the public keys inside are
|
||||
// specified in the form of HD chains (xpubs).
|
||||
//
|
||||
// Descriptors always represent public information - public keys and scripts -
|
||||
// but in cases where private keys need to be conveyed along with a descriptor,
|
||||
// they can be included inside by changing public keys to private keys (WIF
|
||||
// format), and changing xpubs by xprvs.
|
||||
//
|
||||
// 1. Examples
|
||||
//
|
||||
// A P2PK descriptor with a fixed public key:
|
||||
// - pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)
|
||||
//
|
||||
// A P2SH-P2WSH-P2PKH descriptor with a fixed public key:
|
||||
// - sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))
|
||||
//
|
||||
// A bare 1-of-2 multisig descriptor:
|
||||
// - multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)
|
||||
//
|
||||
// A chain of P2PKH outputs (this needs the corresponding private key to derive):
|
||||
// - pkh(xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw/1'/2/*)
|
||||
//
|
||||
// 2. Grammar description:
|
||||
//
|
||||
// X: xpub or xprv encoded extended key
|
||||
// I: decimal encoded integer
|
||||
// H: Hex encoded byte array
|
||||
// A: Address in P2PKH, P2SH, or Bech32 encoding
|
||||
//
|
||||
// S (Scripts):
|
||||
// * pk(P): Pay-to-pubkey (P2PK) output for public key P.
|
||||
// * pkh(P): Pay-to-pubkey-hash (P2PKH) output for public key P.
|
||||
// * sh(S): Pay-to-script-hash (P2SH) output for script S
|
||||
// * combo(P): combination of P2PK and P2PKH for public key P.
|
||||
// * multi(I,L): k-of-n multisig for given public keys
|
||||
// * addr(A): Output to address
|
||||
// * raw(H): scriptPubKey with raw bytes
|
||||
//
|
||||
// P (Public keys):
|
||||
// * H: fixed public key (or WIF-encoded private key)
|
||||
// * E: extended public key
|
||||
// * E/*: (ranged) all unhardened direct children of an extended public key
|
||||
// * E/*': (ranged) all hardened direct children of an extended public key
|
||||
//
|
||||
// L (Comma-separated lists of public keys):
|
||||
// * P
|
||||
// * L,P
|
||||
//
|
||||
// E (Extended public keys):
|
||||
// * X
|
||||
// * E/I: unhardened child
|
||||
// * E/I': hardened child
|
||||
// * E/Ih: hardened child (alternative notation)
|
||||
//
|
||||
// The top level is S.
|
||||
|
||||
/** Interface for parsed descriptor objects. */
|
||||
struct Descriptor {
|
||||
virtual ~Descriptor() = default;
|
||||
|
||||
/** Whether the expansion of this descriptor depends on the position. */
|
||||
virtual bool IsRange() const = 0;
|
||||
|
||||
/** Convert the descriptor back to a string, undoing parsing. */
|
||||
virtual std::string ToString() const = 0;
|
||||
|
||||
/** Convert the descriptor to a private string. This fails if the provided provider does not have the relevant private keys. */
|
||||
virtual bool ToPrivateString(const SigningProvider& provider, std::string& out) const = 0;
|
||||
|
||||
/** Expand a descriptor at a specified position.
|
||||
*
|
||||
* pos: the position at which to expand the descriptor. If IsRange() is false, this is ignored.
|
||||
* provider: the provider to query for private keys in case of hardened derivation.
|
||||
* output_script: the expanded scriptPubKeys will be put here.
|
||||
* out: scripts and public keys necessary for solving the expanded scriptPubKeys will be put here (may be equal to provider).
|
||||
*/
|
||||
virtual bool Expand(int pos, const SigningProvider& provider, std::vector<CScript>& output_scripts, FlatSigningProvider& out) const = 0;
|
||||
};
|
||||
|
||||
/** Parse a descriptor string. Included private keys are put in out. Returns nullptr if parsing fails. */
|
||||
std::unique_ptr<Descriptor> Parse(const std::string& descriptor, FlatSigningProvider& out);
|
||||
|
||||
#endif // BITCOIN_SCRIPT_DESCRIPTOR_H
|
@ -11,7 +11,6 @@
|
||||
#include <script/standard.h>
|
||||
#include <uint256.h>
|
||||
|
||||
|
||||
typedef std::vector<unsigned char> valtype;
|
||||
|
||||
MutableTransactionSignatureCreator::MutableTransactionSignatureCreator(const CMutableTransaction* txToIn, unsigned int nInIn, const CAmount& amountIn, int nHashTypeIn) : txTo(txToIn), nIn(nInIn), nHashType(nHashTypeIn), amount(amountIn), checker(txTo, nIn, amountIn) {}
|
||||
@ -389,6 +388,18 @@ public:
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename M, typename K, typename V>
|
||||
bool LookupHelper(const M& map, const K& key, V& value)
|
||||
{
|
||||
auto it = map.find(key);
|
||||
if (it != map.end()) {
|
||||
value = it->second;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const BaseSignatureCreator& DUMMY_SIGNATURE_CREATOR = DummySignatureCreator(32, 32);
|
||||
@ -410,7 +421,6 @@ bool IsSolvable(const SigningProvider& provider, const CScript& script)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
bool PartiallySignedTransaction::IsNull() const
|
||||
{
|
||||
return !tx && inputs.empty() && outputs.empty() && unknown.empty();
|
||||
@ -552,3 +562,19 @@ bool HidingSigningProvider::GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& inf
|
||||
if (m_hide_origin) return false;
|
||||
return m_provider->GetKeyOrigin(keyid, info);
|
||||
}
|
||||
|
||||
bool FlatSigningProvider::GetCScript(const CScriptID& scriptid, CScript& script) const { return LookupHelper(scripts, scriptid, script); }
|
||||
bool FlatSigningProvider::GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const { return LookupHelper(pubkeys, keyid, pubkey); }
|
||||
bool FlatSigningProvider::GetKey(const CKeyID& keyid, CKey& key) const { return LookupHelper(keys, keyid, key); }
|
||||
|
||||
FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b)
|
||||
{
|
||||
FlatSigningProvider ret;
|
||||
ret.scripts = a.scripts;
|
||||
ret.scripts.insert(b.scripts.begin(), b.scripts.end());
|
||||
ret.pubkeys = a.pubkeys;
|
||||
ret.pubkeys.insert(b.pubkeys.begin(), b.pubkeys.end());
|
||||
ret.keys = a.keys;
|
||||
ret.keys.insert(b.keys.begin(), b.keys.end());
|
||||
return ret;
|
||||
}
|
||||
|
@ -54,6 +54,19 @@ public:
|
||||
bool GetKeyOrigin(const CKeyID& keyid, KeyOriginInfo& info) const override;
|
||||
};
|
||||
|
||||
struct FlatSigningProvider final : public SigningProvider
|
||||
{
|
||||
std::map<CScriptID, CScript> scripts;
|
||||
std::map<CKeyID, CPubKey> pubkeys;
|
||||
std::map<CKeyID, CKey> keys;
|
||||
|
||||
bool GetCScript(const CScriptID& scriptid, CScript& script) const override;
|
||||
bool GetPubKey(const CKeyID& keyid, CPubKey& pubkey) const override;
|
||||
bool GetKey(const CKeyID& keyid, CKey& key) const override;
|
||||
};
|
||||
|
||||
FlatSigningProvider Merge(const FlatSigningProvider& a, const FlatSigningProvider& b);
|
||||
|
||||
/** Interface for signature creators. */
|
||||
class BaseSignatureCreator {
|
||||
public:
|
||||
|
145
src/test/descriptor_tests.cpp
Normal file
145
src/test/descriptor_tests.cpp
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2018 The Bitcoin Core developers
|
||||
// Distributed under the MIT software license, see the accompanying
|
||||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <script/sign.h>
|
||||
#include <script/standard.h>
|
||||
#include <test/test_dash.h>
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <script/descriptor.h>
|
||||
#include <util/strencodings.h>
|
||||
|
||||
namespace {
|
||||
|
||||
void CheckUnparsable(const std::string& prv, const std::string& pub)
|
||||
{
|
||||
FlatSigningProvider keys_priv, keys_pub;
|
||||
auto parse_priv = Parse(prv, keys_priv);
|
||||
auto parse_pub = Parse(pub, keys_pub);
|
||||
BOOST_CHECK(!parse_priv);
|
||||
BOOST_CHECK(!parse_pub);
|
||||
}
|
||||
|
||||
constexpr int DEFAULT = 0;
|
||||
constexpr int RANGE = 1; // Expected to be ranged descriptor
|
||||
constexpr int HARDENED = 2; // Derivation needs access to private keys
|
||||
constexpr int UNSOLVABLE = 4; // This descriptor is not expected to be solvable
|
||||
constexpr int SIGNABLE = 8; // We can sign with this descriptor (this is not true when actual BIP32 derivation is used, as that's not integrated in our signing code)
|
||||
|
||||
std::string MaybeUseHInsteadOfApostrophy(std::string ret)
|
||||
{
|
||||
if (InsecureRandBool()) {
|
||||
while (true) {
|
||||
auto it = ret.find("'");
|
||||
if (it != std::string::npos) {
|
||||
ret[it] = 'h';
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void Check(const std::string& prv, const std::string& pub, int flags, const std::vector<std::vector<std::string>>& scripts)
|
||||
{
|
||||
FlatSigningProvider keys_priv, keys_pub;
|
||||
|
||||
// Check that parsing succeeds.
|
||||
std::unique_ptr<Descriptor> parse_priv = Parse(MaybeUseHInsteadOfApostrophy(prv), keys_priv);
|
||||
std::unique_ptr<Descriptor> parse_pub = Parse(MaybeUseHInsteadOfApostrophy(pub), keys_pub);
|
||||
BOOST_CHECK(parse_priv);
|
||||
BOOST_CHECK(parse_pub);
|
||||
|
||||
// Check private keys are extracted from the private version but not the public one.
|
||||
BOOST_CHECK(keys_priv.keys.size());
|
||||
BOOST_CHECK(!keys_pub.keys.size());
|
||||
|
||||
// Check that both versions serialize back to the public version.
|
||||
std::string pub1 = parse_priv->ToString();
|
||||
std::string pub2 = parse_priv->ToString();
|
||||
BOOST_CHECK_EQUAL(pub, pub1);
|
||||
BOOST_CHECK_EQUAL(pub, pub2);
|
||||
|
||||
// Check that both can be serialized with private key back to the private version, but not without private key.
|
||||
std::string prv1, prv2;
|
||||
BOOST_CHECK(parse_priv->ToPrivateString(keys_priv, prv1));
|
||||
BOOST_CHECK_EQUAL(prv, prv1);
|
||||
BOOST_CHECK(!parse_priv->ToPrivateString(keys_pub, prv1));
|
||||
BOOST_CHECK(parse_pub->ToPrivateString(keys_priv, prv1));
|
||||
BOOST_CHECK_EQUAL(prv, prv1);
|
||||
BOOST_CHECK(!parse_pub->ToPrivateString(keys_pub, prv1));
|
||||
|
||||
// Check whether IsRange on both returns the expected result
|
||||
BOOST_CHECK_EQUAL(parse_pub->IsRange(), (flags & RANGE) != 0);
|
||||
BOOST_CHECK_EQUAL(parse_priv->IsRange(), (flags & RANGE) != 0);
|
||||
|
||||
|
||||
// Is not ranged descriptor, only a single result is expected.
|
||||
if (!(flags & RANGE)) assert(scripts.size() == 1);
|
||||
|
||||
size_t max = (flags & RANGE) ? scripts.size() : 3;
|
||||
for (size_t i = 0; i < max; ++i) {
|
||||
const auto& ref = scripts[(flags & RANGE) ? i : 0];
|
||||
for (int t = 0; t < 2; ++t) {
|
||||
FlatSigningProvider key_provider = (flags & HARDENED) ? keys_priv : keys_pub;
|
||||
FlatSigningProvider script_provider;
|
||||
std::vector<CScript> spks;
|
||||
BOOST_CHECK((t ? parse_priv : parse_pub)->Expand(i, key_provider, spks, script_provider));
|
||||
BOOST_CHECK_EQUAL(spks.size(), ref.size());
|
||||
for (size_t n = 0; n < spks.size(); ++n) {
|
||||
BOOST_CHECK_EQUAL(ref[n], HexStr(spks[n]));
|
||||
|
||||
if (flags & SIGNABLE) {
|
||||
CMutableTransaction spend;
|
||||
spend.vin.resize(1);
|
||||
spend.vout.resize(1);
|
||||
BOOST_CHECK_MESSAGE(SignSignature(Merge(keys_priv, script_provider), spks[n], spend, 0, 1, SIGHASH_ALL), prv);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
BOOST_FIXTURE_TEST_SUITE(descriptor_tests, BasicTestingSetup)
|
||||
|
||||
BOOST_AUTO_TEST_CASE(descriptor_test)
|
||||
{
|
||||
// Basic single-key compressed
|
||||
Check("pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac"}});
|
||||
Check("pkh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}});
|
||||
Check("combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)", SIGNABLE, {{"2103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bdac","76a9149a1c78a507689f6f54b847ad1cef1e614ee23f1e88ac"}});
|
||||
|
||||
// Basic single-key uncompressed
|
||||
Check("pk(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "pk(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac"}});
|
||||
Check("pkh(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "pkh(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
|
||||
Check("combo(7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "combo(04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235ac","76a914b5bd079c4d57cc7fc28ecf8213a6b791625b818388ac"}});
|
||||
|
||||
// Some unconventional single-key constructions
|
||||
Check("sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141857af51a5e516552b3086430fd8ce55f7c1a52487"}});
|
||||
Check("sh(pkh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(pkh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))", SIGNABLE, {{"a9141a31ad23bf49c247dd531a623c2ef57da3c400c587"}});
|
||||
|
||||
// Versions with BIP32 derivations
|
||||
Check("pk(xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0)", "pk(xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0)", DEFAULT, {{"210379e45b3cf75f9c5f9befd8e9506fb962f6a9d185ac87001ec44a8d3df8d4a9e3ac"}});
|
||||
Check("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483647'/0)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483647'/0)", HARDENED, {{"76a914ebdc90806a9c4356c1c88e42216611e1cb4c1c1788ac"}});
|
||||
Check("combo(xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc)", "combo(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL)", SIGNABLE, {{"2102d2b36900396c9282fa14628566582f206a5dd0bcc8d5e892611806cafb0301f0ac","76a91431a507b815593dfc51ffc7245ae7e5aee304246e88ac"}});
|
||||
Check("combo(xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334/*)", "combo(xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV/*)", RANGE, {{"2102df12b7035bdac8e3bab862a3a83d06ea6b17b6753d52edecba9be46f5d09e076ac","76a914f90e3178ca25f2c808dc76624032d352fdbdfaf288ac"},{"21032869a233c9adff9a994e4966e5b821fd5bac066da6c3112488dc52383b4a98ecac","76a914a8409d1b6dfb1ed2a3e8aa5e0ef2ff26b15b75b788ac"}});
|
||||
CheckUnparsable("pkh(xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U/2147483648)", "pkh(xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB/2147483648)"); // BIP 32 path element overflow
|
||||
|
||||
// Multisig constructions
|
||||
Check("multi(1,XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2,7sH936MDoVPFVk2VoaCM5yW8P3BfPyffnZECyaHrZwfLgWpS13e)", "multi(1,03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd,04a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea235)", SIGNABLE, {{"512103a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd4104a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd5b8dec5235a0fa8722476c7709c02559e3aa73aa03918ba2d492eea75abea23552ae"}});
|
||||
Check("sh(multi(2,xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", "sh(multi(2,xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", DEFAULT, {{"a91445a9a622a8b0a1269944be477640eedc447bbd8487"}});
|
||||
CheckUnparsable("sh(multi(16,KzoAz5CanayRKex3fSLQ2BwJpN7U52gZvxMyk78nDMHuqrUxuSJy,KwGNz6YCCQtYvFzMtrC6D3tKTKdBBboMrLTsjr2NYVBwapCkn7Mr,KxogYhiNfwxuswvXV66eFyKcCpm7dZ7TqHVqujHAVUjJxyivxQ9X,L2BUNduTSyZwZjwNHynQTF14mv2uz2NRq5n5sYWTb4FkkmqgEE9f,L1okJGHGn1kFjdXHKxXjwVVtmCMR2JA5QsbKCSpSb7ReQjezKeoD,KxDCNSST75HFPaW5QKpzHtAyaCQC7p9Vo3FYfi2u4dXD1vgMiboK,L5edQjFtnkcf5UWURn6UuuoFrabgDQUHdheKCziwN42aLwS3KizU,KzF8UWFcEC7BYTq8Go1xVimMkDmyNYVmXV5PV7RuDicvAocoPB8i,L3nHUboKG2w4VSJ5jYZ5CBM97oeK6YuKvfZxrefdShECcjEYKMWZ,KyjHo36dWkYhimKmVVmQTq3gERv3pnqA4xFCpvUgbGDJad7eS8WE,KwsfyHKRUTZPQtysN7M3tZ4GXTnuov5XRgjdF2XCG8faAPmFruRF,KzCUbGhN9LJhdeFfL9zQgTJMjqxdBKEekRGZX24hXdgCNCijkkap,KzgpMBwwsDLwkaC5UrmBgCYaBD2WgZ7PBoGYXR8KT7gCA9UTN5a3,KyBXTPy4T7YG4q9tcAM3LkvfRpD1ybHMvcJ2ehaWXaSqeGUxEdkP,KzJDe9iwJRPtKP2F2AoN6zBgzS7uiuAwhWCfGdNeYJ3PC1HNJ8M8,L1xbHrxynrqLKkoYc4qtoQPx6uy5qYXR5ZDYVYBSRmCV5piU3JG9))","sh(multi(16,03669b8afcec803a0d323e9a17f3ea8e68e8abe5a278020a929adbec52421adbd0,0260b2003c386519fc9eadf2b5cf124dd8eea4c4e68d5e154050a9346ea98ce600,0362a74e399c39ed5593852a30147f2959b56bb827dfa3e60e464b02ccf87dc5e8,0261345b53de74a4d721ef877c255429961b7e43714171ac06168d7e08c542a8b8,02da72e8b46901a65d4374fe6315538d8f368557dda3a1dcf9ea903f3afe7314c8,0318c82dd0b53fd3a932d16e0ba9e278fcc937c582d5781be626ff16e201f72286,0297ccef1ef99f9d73dec9ad37476ddb232f1238aff877af19e72ba04493361009,02e502cfd5c3f972fe9a3e2a18827820638f96b6f347e54d63deb839011fd5765d,03e687710f0e3ebe81c1037074da939d409c0025f17eb86adb9427d28f0f7ae0e9,02c04d3a5274952acdbc76987f3184b346a483d43be40874624b29e3692c1df5af,02ed06e0f418b5b43a7ec01d1d7d27290fa15f75771cb69b642a51471c29c84acd,036d46073cbb9ffee90473f3da429abc8de7f8751199da44485682a989a4bebb24,02f5d1ff7c9029a80a4e36b9a5497027ef7f3e73384a4a94fbfe7c4e9164eec8bc,02e41deffd1b7cce11cde209a781adcffdabd1b91c0ba0375857a2bfd9302419f3,02d76625f7956a7fc505ab02556c23ee72d832f1bac391bcd2d3abce5710a13d06,0399eb0a5487515802dc14544cf10b3666623762fbed2ec38a3975716e2c29c232))"); // P2SH does not fit 16 compressed pubkeys in a redeemscript
|
||||
|
||||
// Check for invalid nesting of structures
|
||||
CheckUnparsable("sh(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)", "sh(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)"); // P2SH needs a script, not a key
|
||||
CheckUnparsable("sh(sh(pk(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2)))", "sh(sh(pk(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd)))"); // Cannot embed P2SH inside P2SH
|
||||
CheckUnparsable("sh(combo(XJvEUEcFWCHCyruc8ZX5exPZaGe4UR7gC5FHrhwPnQGDs1uWCsT2))", "sh(combo(03a34b99f22c790c4e36b2b3c2c35a36db06226e41c692fc82b8b56ac1c540c5bd))"); // Old must be top level
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
89
test/functional/rpc_scantxoutset.py
Executable file
89
test/functional/rpc_scantxoutset.py
Executable file
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2018 The Bitcoin Core developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
"""Test the scantxoutset rpc call."""
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.util import assert_equal, Decimal
|
||||
|
||||
import shutil
|
||||
import os
|
||||
|
||||
class ScantxoutsetTest(BitcoinTestFramework):
|
||||
def set_test_params(self):
|
||||
self.num_nodes = 1
|
||||
self.setup_clean_chain = True
|
||||
def run_test(self):
|
||||
self.log.info("Mining blocks...")
|
||||
self.nodes[0].generate(110)
|
||||
|
||||
addr1 = self.nodes[0].getnewaddress("")
|
||||
pubk1 = self.nodes[0].getaddressinfo(addr1)['pubkey']
|
||||
addr2 = self.nodes[0].getnewaddress("")
|
||||
pubk2 = self.nodes[0].getaddressinfo(addr2)['pubkey']
|
||||
addr3 = self.nodes[0].getnewaddress("")
|
||||
pubk3 = self.nodes[0].getaddressinfo(addr3)['pubkey']
|
||||
self.nodes[0].sendtoaddress(addr1, 0.001)
|
||||
self.nodes[0].sendtoaddress(addr2, 0.002)
|
||||
self.nodes[0].sendtoaddress(addr3, 0.004)
|
||||
|
||||
#send to child keys of tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK
|
||||
self.nodes[0].sendtoaddress("yR5yZLjevw5kX3UxGiQN1g96LXGJni2wSS", 0.008) # (m/0'/0'/0')
|
||||
self.nodes[0].sendtoaddress("yPcxzaQekxTjaJVSaZ58r22o37H8moWPK2", 0.016) # (m/0'/0'/1')
|
||||
self.nodes[0].sendtoaddress("yhv7iRHSx4SgyvCPmkm6Js8gTuJTtJH9ec", 0.032) # (m/0'/0'/1500')
|
||||
self.nodes[0].sendtoaddress("yWEdyyKVNbmaiXHkg3LVPqgoXpMA3S6Xt7", 0.064) # (m/0'/0'/0)
|
||||
self.nodes[0].sendtoaddress("yTGAdq8sSHJ1QrcqSUaMHs8RMj3Bqz3bkb", 0.128) # (m/0'/0'/1)
|
||||
self.nodes[0].sendtoaddress("yRTNkmjXjhatND4Dv3V4GwBzYJQ4o9ukQr", 0.256) # (m/0'/0'/1500)
|
||||
self.nodes[0].sendtoaddress("yPwUp9Vwmr4zE6rSuZg3TBxeyRerdRAbNd", 0.512) # (m/1/1/0')
|
||||
self.nodes[0].sendtoaddress("yLapNU3bG8E8JNGXRhZbRHHDifrqTucGcg", 1.024) # (m/1/1/1')
|
||||
self.nodes[0].sendtoaddress("yUhbAKf7AcTC5sPXb2dkABKm3FYENqdzv2", 2.048) # (m/1/1/1500')
|
||||
self.nodes[0].sendtoaddress("yZTyMdEJjZWJi6CwY6g3WurLESH3UsWrrM", 4.096) # (m/1/1/0)
|
||||
self.nodes[0].sendtoaddress("ydccVGNV2EcEouAxbbgdu8pi8gkdaqkiav", 8.192) # (m/1/1/1)
|
||||
self.nodes[0].sendtoaddress("yVCdQxPXJ3SrtTLv8FuLXDNaynz6kmjPNq", 16.384) # (m/1/1/1500)
|
||||
|
||||
|
||||
self.nodes[0].generate(1)
|
||||
|
||||
self.log.info("Stop node, remove wallet, mine again some blocks...")
|
||||
self.stop_node(0)
|
||||
shutil.rmtree(os.path.join(self.nodes[0].datadir, "regtest", 'wallets'))
|
||||
self.start_node(0)
|
||||
self.nodes[0].generate(110)
|
||||
|
||||
self.restart_node(0, ['-nowallet'])
|
||||
self.log.info("Test if we have found the non HD unspent outputs.")
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "pkh(" + pubk1 + ")", "pkh(" + pubk2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(" + pubk1 + ")", "combo(" + pubk2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "combo(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "addr(" + addr3 + ")"])['total_amount'], Decimal("0.007"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "addr(" + addr1 + ")", "addr(" + addr2 + ")", "pkh(" + pubk3 + ")"])['total_amount'], Decimal("0.007"))
|
||||
|
||||
self.log.info("Test extended key derivation.")
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/0h)"])['total_amount'], Decimal("0.008"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/1h)"])['total_amount'], Decimal("0.016"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500')"])['total_amount'], Decimal("0.032"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0h/0)"])['total_amount'], Decimal("0.064"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/1)"])['total_amount'], Decimal("0.128"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/1500)"])['total_amount'], Decimal("0.256"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*h)", "range": 1499}])['total_amount'], Decimal("0.024"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0'/*h)", "range": 1500}])['total_amount'], Decimal("0.056"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0h/0'/*)", "range": 1499}])['total_amount'], Decimal("0.192"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/0'/0h/*)", "range": 1500}])['total_amount'], Decimal("0.448"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0')"])['total_amount'], Decimal("0.512"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1')"])['total_amount'], Decimal("1.024"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500h)"])['total_amount'], Decimal("2.048"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/0)"])['total_amount'], Decimal("4.096"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1)"])['total_amount'], Decimal("8.192"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/1500)"])['total_amount'], Decimal("16.384"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/0)"])['total_amount'], Decimal("4.096"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1)"])['total_amount'], Decimal("8.192"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/1500)"])['total_amount'], Decimal("16.384"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1499}])['total_amount'], Decimal("1.536"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*')", "range": 1500}])['total_amount'], Decimal("3.584"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tprv8ZgxMBicQKsPd7Uf69XL1XwhmjHopUGep8GuEiJDZmbQz6o58LninorQAfcKZWARbtRtfnLcJ5MQ2AtHcQJCCRUcMRvmDUjyEmNUWwx8UbK/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1499}])['total_amount'], Decimal("12.288"))
|
||||
assert_equal(self.nodes[0].scantxoutset("start", [ {"desc": "combo(tpubD6NzVbkrYhZ4WaWSyoBvQwbpLkojyoTZPRsgXELWz3Popb3qkjcJyJUGLnL4qHHoQvao8ESaAstxYSnhyswJ76uZPStJRJCTKvosUCJZL5B/1/1/*)", "range": 1500}])['total_amount'], Decimal("28.672"))
|
||||
|
||||
if __name__ == '__main__':
|
||||
ScantxoutsetTest().main()
|
@ -193,6 +193,7 @@ BASE_SCRIPTS = [
|
||||
'p2p_unrequested_blocks.py',
|
||||
'feature_asmap.py',
|
||||
'feature_includeconf.py',
|
||||
'rpc_scantxoutset.py',
|
||||
'feature_logging.py',
|
||||
'p2p_node_network_limited.py',
|
||||
'p2p_permissions.py',
|
||||
|
Loading…
Reference in New Issue
Block a user