mirror of
https://github.com/ElementsProject/elements.git
synced 2026-08-16 13:01:19 +02:00
Merge UP TO 48174c0f28 into merged_master (UP TO Bitcoin PR bitcoin/bitcoin#26240)
FIXME in wallet_taproot.py functional test 1668424146 2022-11-14T12:09:06+01:0048174c0f28Bitcoin Merge bitcoin/bitcoin#26240: rpc: Adjust RPCTypeCheckObj error string 1668417474 2022-11-14T10:17:54+01:0059e00c7e03Bitcoin Merge bitcoin/bitcoin#25714: univalue: Avoid std::string copies 1668111238 2022-11-10T15:13:58-05:007ef730ca84Bitcoin Merge bitcoin/bitcoin#26483: test: Don't pass add_to_wallet option to walletcreatefundedpsbt 1668004459 2022-11-09T15:34:19+01:009dce30194bBitcoin Merge bitcoin/bitcoin#26472: test: add missing bech32m / BIP86 test-cases to wallet_descriptor.py 1667992471 2022-11-09T12:14:31+01:0044ca5d5e87Bitcoin Merge bitcoin/bitcoin#26473: test: Avoid collision with valid path names in `getarg_tests/logargs` 1667640759 2022-11-05T10:32:39+01:0050422b770aBitcoin Merge bitcoin/bitcoin#26419: log: mempool: log removal reason in validation interface 1667636699 2022-11-05T09:24:59+01:00ce57dbac90Bitcoin Merge bitcoin/bitcoin#26449: rpc: doc: add missing option "bech32m" for `change_type` parameters 1667577253 2022-11-04T15:54:13+00:00ae6bb6e71eBitcoin Merge bitcoin/bitcoin#26418: Fix signing of multi_a and rawtr scripts with wallets that only have corresponding keys
This commit is contained in:
commit
7ea69ff0d8
18 changed files with 193 additions and 114 deletions
|
|
@ -70,11 +70,8 @@ void RPCTypeCheckObj(const UniValue& o,
|
|||
if (!fAllowNull && v.isNull())
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Missing %s", t.first));
|
||||
|
||||
if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull()))) {
|
||||
std::string err = strprintf("Expected type %s for %s, got %s",
|
||||
uvTypeName(t.second.type), t.first, uvTypeName(v.type()));
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, err);
|
||||
}
|
||||
if (!(t.second.typeAny || v.type() == t.second.type || (fAllowNull && v.isNull())))
|
||||
throw JSONRPCError(RPC_TYPE_ERROR, strprintf("JSON value of type %s for field %s is not of expected type %s", uvTypeName(v.type()), t.first, uvTypeName(t.second.type)));
|
||||
}
|
||||
|
||||
if (fStrict)
|
||||
|
|
|
|||
|
|
@ -150,6 +150,16 @@ static bool CreateSig(const BaseSignatureCreator& creator, SignatureData& sigdat
|
|||
|
||||
static bool CreateTaprootScriptSig(const BaseSignatureCreator& creator, SignatureData& sigdata, const SigningProvider& provider, std::vector<unsigned char>& sig_out, const XOnlyPubKey& pubkey, const uint256& leaf_hash, SigVersion sigversion)
|
||||
{
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(pubkey, info)) {
|
||||
auto it = sigdata.taproot_misc_pubkeys.find(pubkey);
|
||||
if (it == sigdata.taproot_misc_pubkeys.end()) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info));
|
||||
} else {
|
||||
it->second.first.insert(leaf_hash);
|
||||
}
|
||||
}
|
||||
|
||||
auto lookup_key = std::make_pair(pubkey, leaf_hash);
|
||||
auto it = sigdata.taproot_script_sigs.find(lookup_key);
|
||||
if (it != sigdata.taproot_script_sigs.end()) {
|
||||
|
|
@ -174,17 +184,6 @@ static bool SignTaprootScript(const SigningProvider& provider, const BaseSignatu
|
|||
// <xonly pubkey> OP_CHECKSIG
|
||||
if (script.size() == 34 && script[33] == OP_CHECKSIG && script[0] == 0x20) {
|
||||
XOnlyPubKey pubkey{Span{script}.subspan(1, 32)};
|
||||
|
||||
KeyOriginInfo info;
|
||||
if (provider.GetKeyOriginByXOnly(pubkey, info)) {
|
||||
auto it = sigdata.taproot_misc_pubkeys.find(pubkey);
|
||||
if (it == sigdata.taproot_misc_pubkeys.end()) {
|
||||
sigdata.taproot_misc_pubkeys.emplace(pubkey, std::make_pair(std::set<uint256>({leaf_hash}), info));
|
||||
} else {
|
||||
it->second.first.insert(leaf_hash);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> sig;
|
||||
if (CreateTaprootScriptSig(creator, sigdata, provider, sig, pubkey, leaf_hash, sigversion)) {
|
||||
result = Vector(std::move(sig));
|
||||
|
|
|
|||
|
|
@ -429,7 +429,7 @@ BOOST_AUTO_TEST_CASE(logargs)
|
|||
const auto okaylog = std::make_pair("-okaylog", ArgsManager::ALLOW_ANY);
|
||||
const auto dontlog = std::make_pair("-dontlog", ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE);
|
||||
SetupArgs(local_args, {okaylog_bool, okaylog_negbool, okaylog, dontlog});
|
||||
ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private");
|
||||
ResetArgs(local_args, "-okaylog-bool -nookaylog-negbool -okaylog=public -dontlog=private42");
|
||||
|
||||
// Everything logged to debug.log will also append to str
|
||||
std::string str;
|
||||
|
|
@ -447,7 +447,7 @@ BOOST_AUTO_TEST_CASE(logargs)
|
|||
BOOST_CHECK(str.find("Command-line arg: okaylog-negbool=false") != std::string::npos);
|
||||
BOOST_CHECK(str.find("Command-line arg: okaylog=\"public\"") != std::string::npos);
|
||||
BOOST_CHECK(str.find("dontlog=****") != std::string::npos);
|
||||
BOOST_CHECK(str.find("private") == std::string::npos);
|
||||
BOOST_CHECK(str.find("private42") == std::string::npos);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
|
|
@ -1281,3 +1281,17 @@ void CTxMemPool::SetLoadTried(bool load_tried)
|
|||
LOCK(cs);
|
||||
m_load_tried = load_tried;
|
||||
}
|
||||
|
||||
|
||||
const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept
|
||||
{
|
||||
switch (r) {
|
||||
case MemPoolRemovalReason::EXPIRY: return "expiry";
|
||||
case MemPoolRemovalReason::SIZELIMIT: return "sizelimit";
|
||||
case MemPoolRemovalReason::REORG: return "reorg";
|
||||
case MemPoolRemovalReason::BLOCK: return "block";
|
||||
case MemPoolRemovalReason::CONFLICT: return "conflict";
|
||||
case MemPoolRemovalReason::REPLACED: return "replaced";
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -412,6 +412,8 @@ enum class MemPoolRemovalReason {
|
|||
REPLACED, //!< Removed for replacement
|
||||
};
|
||||
|
||||
const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept;
|
||||
|
||||
/**
|
||||
* CTxMemPool stores valid-according-to-the-current-best-chain transactions
|
||||
* that may be included in the next block.
|
||||
|
|
|
|||
|
|
@ -25,10 +25,7 @@ public:
|
|||
};
|
||||
|
||||
UniValue() { typ = VNULL; }
|
||||
UniValue(UniValue::VType initialType, const std::string& initialStr = "") {
|
||||
typ = initialType;
|
||||
val = initialStr;
|
||||
}
|
||||
UniValue(UniValue::VType type, std::string str = {}) : typ{type}, val{std::move(str)} {}
|
||||
template <typename Ref, typename T = std::remove_cv_t<std::remove_reference_t<Ref>>,
|
||||
std::enable_if_t<std::is_floating_point_v<T> || // setFloat
|
||||
std::is_same_v<bool, T> || // setBool
|
||||
|
|
@ -54,12 +51,12 @@ public:
|
|||
|
||||
void setNull();
|
||||
void setBool(bool val);
|
||||
void setNumStr(const std::string& val);
|
||||
void setNumStr(std::string str);
|
||||
void setInt(uint64_t val);
|
||||
void setInt(int64_t val);
|
||||
void setInt(int val_) { return setInt(int64_t{val_}); }
|
||||
void setFloat(double val);
|
||||
void setStr(const std::string& val);
|
||||
void setStr(std::string str);
|
||||
void setArray();
|
||||
void setObject();
|
||||
|
||||
|
|
|
|||
|
|
@ -44,15 +44,15 @@ static bool validNumStr(const std::string& s)
|
|||
return (tt == JTOK_NUMBER);
|
||||
}
|
||||
|
||||
void UniValue::setNumStr(const std::string& val_)
|
||||
void UniValue::setNumStr(std::string str)
|
||||
{
|
||||
if (!validNumStr(val_)) {
|
||||
throw std::runtime_error{"The string '" + val_ + "' is not a valid JSON number"};
|
||||
if (!validNumStr(str)) {
|
||||
throw std::runtime_error{"The string '" + str + "' is not a valid JSON number"};
|
||||
}
|
||||
|
||||
clear();
|
||||
typ = VNUM;
|
||||
val = val_;
|
||||
val = std::move(str);
|
||||
}
|
||||
|
||||
void UniValue::setInt(uint64_t val_)
|
||||
|
|
@ -82,11 +82,11 @@ void UniValue::setFloat(double val_)
|
|||
return setNumStr(oss.str());
|
||||
}
|
||||
|
||||
void UniValue::setStr(const std::string& val_)
|
||||
void UniValue::setStr(std::string str)
|
||||
{
|
||||
clear();
|
||||
typ = VSTR;
|
||||
val = val_;
|
||||
val = std::move(str);
|
||||
}
|
||||
|
||||
void UniValue::setArray()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#define BOOST_CHECK(expr) assert(expr)
|
||||
|
|
@ -160,6 +161,14 @@ void univalue_set()
|
|||
BOOST_CHECK(v.isStr());
|
||||
BOOST_CHECK_EQUAL(v.getValStr(), "zum");
|
||||
|
||||
{
|
||||
std::string_view sv{"ab\0c", 4};
|
||||
UniValue j{sv};
|
||||
BOOST_CHECK(j.isStr());
|
||||
BOOST_CHECK_EQUAL(j.getValStr(), sv);
|
||||
BOOST_CHECK_EQUAL(j.write(), "\"ab\\u0000c\"");
|
||||
}
|
||||
|
||||
v.setFloat(-1.01);
|
||||
BOOST_CHECK(v.isNum());
|
||||
BOOST_CHECK_EQUAL(v.getValStr(), "-1.01");
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@
|
|||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
const std::string RemovalReasonToString(const MemPoolRemovalReason& r) noexcept;
|
||||
|
||||
/**
|
||||
* MainSignalsImpl manages a list of shared_ptr<CValidationInterface> callbacks.
|
||||
*
|
||||
|
|
@ -215,9 +217,10 @@ void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemP
|
|||
auto event = [tx, reason, mempool_sequence, this] {
|
||||
m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); });
|
||||
};
|
||||
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__,
|
||||
ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s reason=%s", __func__,
|
||||
tx->GetHash().ToString(),
|
||||
tx->GetWitnessHash().ToString());
|
||||
tx->GetWitnessHash().ToString(),
|
||||
RemovalReasonToString(reason));
|
||||
}
|
||||
|
||||
void CMainSignals::BlockConnected(const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex) {
|
||||
|
|
|
|||
|
|
@ -828,7 +828,7 @@ RPCHelpMan fundrawtransaction()
|
|||
"If that happens, you will need to fund the transaction with different inputs and republish it."},
|
||||
{"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The address to receive the change"},
|
||||
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
|
||||
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
|
||||
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
||||
"e.g. with 'importpubkey' or 'importmulti' with the 'pubkeys' or 'desc' field."},
|
||||
|
|
@ -1247,7 +1247,7 @@ RPCHelpMan send()
|
|||
{"add_to_wallet", RPCArg::Type::BOOL, RPCArg::Default{true}, "When false, returns a serialized transaction which will not be added to the wallet or broadcast"},
|
||||
{"change_address", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The address to receive the change"},
|
||||
{"change_position", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if change_address is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\" and \"bech32m\"."},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
{"include_watching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only.\n"
|
||||
"Only solvable inputs can be used. Watch-only destinations are solvable if the public key and/or output script was imported,\n"
|
||||
|
|
@ -1752,7 +1752,7 @@ RPCHelpMan walletcreatefundedpsbt()
|
|||
"If that happens, you will need to fund the transaction with different inputs and republish it."},
|
||||
{"changeAddress", RPCArg::Type::STR, RPCArg::DefaultHint{"automatic"}, "The address to receive the change"},
|
||||
{"changePosition", RPCArg::Type::NUM, RPCArg::DefaultHint{"random"}, "The index of the change output"},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", and \"bech32\"."},
|
||||
{"change_type", RPCArg::Type::STR, RPCArg::DefaultHint{"set by -changetype"}, "The output type to use. Only valid if changeAddress is not specified. Options are \"legacy\", \"p2sh-segwit\", \"bech32\", and \"bech32m\"."},
|
||||
{"includeWatching", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Also select inputs which are watch only"},
|
||||
{"lockUnspents", RPCArg::Type::BOOL, RPCArg::Default{false}, "Lock selected unspent outputs"},
|
||||
{"fee_rate", RPCArg::Type::AMOUNT, RPCArg::DefaultHint{"not set, fall back to wallet fee estimation"}, "Specify a fee rate in " + CURRENCY_ATOM + "/vB."},
|
||||
|
|
|
|||
|
|
@ -2509,14 +2509,23 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
|
|||
keys->Merge(std::move(*script_keys));
|
||||
} else {
|
||||
// Maybe there are pubkeys listed that we can sign for
|
||||
script_keys = std::make_unique<FlatSigningProvider>();
|
||||
for (const auto& pk_pair : input.hd_keypaths) {
|
||||
const CPubKey& pubkey = pk_pair.first;
|
||||
std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
|
||||
if (pk_keys) {
|
||||
keys->Merge(std::move(*pk_keys));
|
||||
}
|
||||
std::vector<CPubKey> pubkeys;
|
||||
|
||||
// ECDSA Pubkeys
|
||||
for (const auto& [pk, _] : input.hd_keypaths) {
|
||||
pubkeys.push_back(pk);
|
||||
}
|
||||
|
||||
// Taproot output pubkey
|
||||
std::vector<std::vector<unsigned char>> sols;
|
||||
if (Solver(script, sols) == TxoutType::WITNESS_V1_TAPROOT) {
|
||||
sols[0].insert(sols[0].begin(), 0x02);
|
||||
pubkeys.emplace_back(sols[0]);
|
||||
sols[0][0] = 0x03;
|
||||
pubkeys.emplace_back(sols[0]);
|
||||
}
|
||||
|
||||
// Taproot pubkeys
|
||||
for (const auto& pk_pair : input.m_tap_bip32_paths) {
|
||||
const XOnlyPubKey& pubkey = pk_pair.first;
|
||||
for (unsigned char prefix : {0x02, 0x03}) {
|
||||
|
|
@ -2524,10 +2533,14 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
|
|||
std::copy(pubkey.begin(), pubkey.end(), b + 1);
|
||||
CPubKey fullpubkey;
|
||||
fullpubkey.Set(b, b + 33);
|
||||
std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey);
|
||||
if (pk_keys) {
|
||||
keys->Merge(std::move(*pk_keys));
|
||||
}
|
||||
pubkeys.push_back(fullpubkey);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pubkey : pubkeys) {
|
||||
std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(pubkey);
|
||||
if (pk_keys) {
|
||||
keys->Merge(std::move(*pk_keys));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -848,7 +848,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
self.log.info("Test fundrawtxn with invalid estimate_mode settings")
|
||||
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
||||
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
|
||||
assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",
|
||||
node.fundrawtransaction, rawtx, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
|
||||
for mode in ["", "foo", Decimal("3.141592")]:
|
||||
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
|
||||
|
|
@ -858,7 +858,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
for mode in ["unset", "economical", "conservative"]:
|
||||
self.log.debug("{}".format(mode))
|
||||
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
||||
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
|
||||
assert_raises_rpc_error(-3, f"JSON value of type {k} for field conf_target is not of expected type number",
|
||||
node.fundrawtransaction, rawtx, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||
for n in [-1, 0, 1009]:
|
||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||
|
|
@ -1255,7 +1255,6 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||
|
||||
# Case (3), Explicit add_inputs=true and preset inputs (with preset inputs not-covering the target amount)
|
||||
options["add_inputs"] = True
|
||||
options["add_to_wallet"] = False
|
||||
assert "psbt" in wallet.walletcreatefundedpsbt(outputs=[{addr1: 8}], inputs=inputs, options=options)
|
||||
|
||||
# Case (4), Explicit add_inputs=true and preset inputs (with preset inputs covering the target amount)
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class RPCMempoolInfoTest(BitcoinTestFramework):
|
|||
assert_raises_rpc_error(-8, "Invalid parameter, vout cannot be negative", self.nodes[0].gettxspendingprevout, [{'txid' : txidA, 'vout' : -1}])
|
||||
|
||||
self.log.info("Invalid txid provided")
|
||||
assert_raises_rpc_error(-3, "Expected type string for txid, got number", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}])
|
||||
assert_raises_rpc_error(-3, "JSON value of type number for field txid is not of expected type string", self.nodes[0].gettxspendingprevout, [{'txid' : 42, 'vout' : 0}])
|
||||
|
||||
self.log.info("Missing outputs")
|
||||
assert_raises_rpc_error(-8, "Invalid parameter, outputs are missing", self.nodes[0].gettxspendingprevout, [])
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
|
||||
self.log.info("- raises RPC error with invalid estimate_mode settings")
|
||||
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
||||
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
|
||||
assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": v, "conf_target": 0.1, "add_inputs": True})
|
||||
for mode in ["", "foo", Decimal("3.141592")]:
|
||||
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
|
||||
|
|
@ -359,7 +359,7 @@ class PSBTTest(BitcoinTestFramework):
|
|||
for mode in ["unset", "economical", "conservative"]:
|
||||
self.log.debug("{}".format(mode))
|
||||
for k, v in {"string": "", "object": {"foo": "bar"}}.items():
|
||||
assert_raises_rpc_error(-3, "Expected type number for conf_target, got {}".format(k),
|
||||
assert_raises_rpc_error(-3, f"JSON value of type {k} for field conf_target is not of expected type number",
|
||||
self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, {"estimate_mode": mode, "conf_target": v, "add_inputs": True})
|
||||
for n in [-1, 0, 1009]:
|
||||
assert_raises_rpc_error(-8, "Invalid conf_target, must be between 1 and 1008", # max value of 1008 per src/policy/fees.h
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ class BumpFeeTest(BitcoinTestFramework):
|
|||
|
||||
self.log.info("Test invalid estimate_mode settings")
|
||||
for k, v in {"number": 42, "object": {"foo": "bar"}}.items():
|
||||
assert_raises_rpc_error(-3, "Expected type string for estimate_mode, got {}".format(k),
|
||||
assert_raises_rpc_error(-3, f"JSON value of type {k} for field estimate_mode is not of expected type string",
|
||||
rbf_node.bumpfee, rbfid, {"estimate_mode": v})
|
||||
for mode in ["foo", Decimal("3.1415"), "sat/B", "BTC/kB"]:
|
||||
assert_raises_rpc_error(-8, 'Invalid estimate_mode parameter, must be one of: "unset", "economical", "conservative"',
|
||||
|
|
|
|||
|
|
@ -62,6 +62,11 @@ class WalletDescriptorTest(BitcoinTestFramework):
|
|||
assert addr_info['desc'].startswith('wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/0/0')
|
||||
|
||||
addr = self.nodes[0].getnewaddress("", "bech32m")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('tr(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/86\'/1\'/0\'/0/0')
|
||||
|
||||
# Check that getrawchangeaddress works
|
||||
addr = self.nodes[0].getrawchangeaddress("legacy")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
|
|
@ -78,6 +83,11 @@ class WalletDescriptorTest(BitcoinTestFramework):
|
|||
assert addr_info['desc'].startswith('wpkh(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/84\'/1\'/0\'/1/0')
|
||||
|
||||
addr = self.nodes[0].getrawchangeaddress("bech32m")
|
||||
addr_info = self.nodes[0].getaddressinfo(addr)
|
||||
assert addr_info['desc'].startswith('tr(')
|
||||
assert_equal(addr_info['hdkeypath'], 'm/86\'/1\'/0\'/1/0')
|
||||
|
||||
# Make a wallet to receive coins at
|
||||
self.nodes[0].createwallet(wallet_name="desc2", descriptors=True)
|
||||
recv_wrpc = self.nodes[0].get_wallet_rpc("desc2")
|
||||
|
|
@ -161,9 +171,11 @@ class WalletDescriptorTest(BitcoinTestFramework):
|
|||
addr_types = [('legacy', False, 'pkh(', '44\'/1\'/0\'', -13),
|
||||
('p2sh-segwit', False, 'sh(wpkh(', '49\'/1\'/0\'', -14),
|
||||
('bech32', False, 'wpkh(', '84\'/1\'/0\'', -13),
|
||||
('bech32m', False, 'tr(', '86\'/1\'/0\'', -13),
|
||||
('legacy', True, 'pkh(', '44\'/1\'/0\'', -13),
|
||||
('p2sh-segwit', True, 'sh(wpkh(', '49\'/1\'/0\'', -14),
|
||||
('bech32', True, 'wpkh(', '84\'/1\'/0\'', -13)]
|
||||
('bech32', True, 'wpkh(', '84\'/1\'/0\'', -13),
|
||||
('bech32m', True, 'tr(', '86\'/1\'/0\'', -13)]
|
||||
|
||||
for addr_type, internal, desc_prefix, deriv_path, int_idx in addr_types:
|
||||
int_str = 'internal' if internal else 'external'
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ class WalletSendTest(BitcoinTestFramework):
|
|||
for mode in ["economical", "conservative"]:
|
||||
for k, v in {"string": "true", "bool": True, "object": {"foo": "bar"}}.items():
|
||||
self.test_send(from_wallet=w0, to_wallet=w1, amount=1, conf_target=v, estimate_mode=mode,
|
||||
expect_error=(-3, f"Expected type number for conf_target, got {k}"))
|
||||
expect_error=(-3, f"JSON value of type {k} for field conf_target is not of expected type number"))
|
||||
|
||||
# Test setting explicit fee rate just below the minimum of 1 sat/vB.
|
||||
self.log.info("Explicit fee rate raises RPC error 'fee rate too low' if fee_rate of 0.99999999 is passed")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"""Test generation and spending of P2TR addresses."""
|
||||
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from decimal import Decimal
|
||||
from test_framework.address import output_key_to_p2tr
|
||||
|
|
@ -226,17 +227,28 @@ class WalletTaprootTest(BitcoinTestFramework):
|
|||
|
||||
def do_test_addr(self, comment, pattern, privmap, treefn, keys):
|
||||
self.log.info("Testing %s address derivation" % comment)
|
||||
|
||||
# Create wallets
|
||||
wallet_uuid = uuid.uuid4().hex
|
||||
self.nodes[0].createwallet(wallet_name=f"privs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True)
|
||||
self.nodes[0].createwallet(wallet_name=f"pubs_tr_enabled_{wallet_uuid}", descriptors=True, blank=True, disable_private_keys=True)
|
||||
self.nodes[0].createwallet(wallet_name=f"addr_gen_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True)
|
||||
privs_tr_enabled = self.nodes[0].get_wallet_rpc(f"privs_tr_enabled_{wallet_uuid}")
|
||||
pubs_tr_enabled = self.nodes[0].get_wallet_rpc(f"pubs_tr_enabled_{wallet_uuid}")
|
||||
addr_gen = self.nodes[0].get_wallet_rpc(f"addr_gen_{wallet_uuid}")
|
||||
|
||||
desc = self.make_desc(pattern, privmap, keys, False)
|
||||
desc_pub = self.make_desc(pattern, privmap, keys, True)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc)['descriptor'], desc_pub)
|
||||
result = self.addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}])
|
||||
result = addr_gen.importdescriptors([{"desc": desc_pub, "active": True, "timestamp": "now"}])
|
||||
assert(result[0]['success'])
|
||||
address_type = "bech32m" if "tr" in pattern else "bech32"
|
||||
for i in range(4):
|
||||
addr_g = self.addr_gen.getnewaddress(address_type='bech32m')
|
||||
addr_g = addr_gen.getnewaddress(address_type=address_type)
|
||||
if treefn is not None:
|
||||
addr_r = self.make_addr(treefn, keys, i)
|
||||
assert_equal(addr_g, addr_r)
|
||||
desc_a = self.addr_gen.getaddressinfo(addr_g)['desc']
|
||||
desc_a = addr_gen.getaddressinfo(addr_g)['desc']
|
||||
if desc.startswith("tr("):
|
||||
assert desc_a.startswith("tr(")
|
||||
rederive = self.nodes[1].deriveaddresses(desc_a)
|
||||
|
|
@ -244,25 +256,37 @@ class WalletTaprootTest(BitcoinTestFramework):
|
|||
assert_equal(rederive[0], addr_g)
|
||||
|
||||
# tr descriptors can be imported
|
||||
result = self.privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
|
||||
result = privs_tr_enabled.importdescriptors([{"desc": desc, "timestamp": "now"}])
|
||||
assert(result[0]["success"])
|
||||
result = self.pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
|
||||
result = pubs_tr_enabled.importdescriptors([{"desc": desc_pub, "timestamp": "now"}])
|
||||
assert(result[0]["success"])
|
||||
|
||||
# Cleanup
|
||||
privs_tr_enabled.unloadwallet()
|
||||
pubs_tr_enabled.unloadwallet()
|
||||
addr_gen.unloadwallet()
|
||||
|
||||
def do_test_sendtoaddress(self, comment, pattern, privmap, treefn, keys_pay, keys_change):
|
||||
self.log.info("Testing %s through sendtoaddress" % comment)
|
||||
|
||||
# Create wallets
|
||||
wallet_uuid = uuid.uuid4().hex
|
||||
self.nodes[0].createwallet(wallet_name=f"rpc_online_{wallet_uuid}", descriptors=True, blank=True)
|
||||
rpc_online = self.nodes[0].get_wallet_rpc(f"rpc_online_{wallet_uuid}")
|
||||
|
||||
desc_pay = self.make_desc(pattern, privmap, keys_pay)
|
||||
desc_change = self.make_desc(pattern, privmap, keys_change)
|
||||
desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True)
|
||||
desc_change_pub = self.make_desc(pattern, privmap, keys_change, True)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
|
||||
result = self.rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
|
||||
result = rpc_online.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
|
||||
assert(result[0]['success'])
|
||||
result = self.rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
|
||||
result = rpc_online.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
|
||||
assert(result[0]['success'])
|
||||
address_type = "bech32m" if "tr" in pattern else "bech32"
|
||||
for i in range(4):
|
||||
addr_g = self.rpc_online.getnewaddress(address_type='bech32m')
|
||||
addr_g = rpc_online.getnewaddress(address_type=address_type)
|
||||
if treefn is not None:
|
||||
addr_r = self.make_addr(treefn, keys_pay, i)
|
||||
assert_equal(addr_g, addr_r)
|
||||
|
|
@ -270,31 +294,52 @@ class WalletTaprootTest(BitcoinTestFramework):
|
|||
to_amnt = random.randrange(1000000, boring_balance)
|
||||
self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
test_balance = int(self.rpc_online.getbalance()['bitcoin'] * 100000000)
|
||||
test_balance = int(rpc_online.getbalance()['bitcoin'] * 100000000)
|
||||
ret_amnt = random.randrange(100000, test_balance)
|
||||
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
|
||||
res = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200)
|
||||
res = rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=Decimal(ret_amnt) / 100000000, subtractfeefromamount=True, fee_rate=200)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
assert(self.rpc_online.gettransaction(res)["confirmations"] > 0)
|
||||
assert(rpc_online.gettransaction(res)["confirmations"] > 0)
|
||||
|
||||
# Cleanup
|
||||
txid = rpc_online.sendall(recipients=[self.boring.getnewaddress()])["txid"]
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
print(txid)
|
||||
# assert(rpc_online.gettransaction(txid)["confirmations"] > 0) # ELEMENTS FIXME: investigate this with fixes to sendall
|
||||
rpc_online.unloadwallet()
|
||||
|
||||
def do_test_psbt(self, comment, pattern, privmap, treefn, keys_pay, keys_change):
|
||||
self.log.info("Testing %s through PSBT" % comment)
|
||||
|
||||
# Create wallets
|
||||
wallet_uuid = uuid.uuid4().hex
|
||||
self.nodes[0].createwallet(wallet_name=f"psbt_online_{wallet_uuid}", descriptors=True, disable_private_keys=True, blank=True)
|
||||
self.nodes[1].createwallet(wallet_name=f"psbt_offline_{wallet_uuid}", descriptors=True, blank=True)
|
||||
self.nodes[1].createwallet(f"key_only_wallet_{wallet_uuid}", descriptors=True, blank=True)
|
||||
psbt_online = self.nodes[0].get_wallet_rpc(f"psbt_online_{wallet_uuid}")
|
||||
psbt_offline = self.nodes[1].get_wallet_rpc(f"psbt_offline_{wallet_uuid}")
|
||||
key_only_wallet = self.nodes[1].get_wallet_rpc(f"key_only_wallet_{wallet_uuid}")
|
||||
|
||||
desc_pay = self.make_desc(pattern, privmap, keys_pay, False)
|
||||
desc_change = self.make_desc(pattern, privmap, keys_change, False)
|
||||
desc_pay_pub = self.make_desc(pattern, privmap, keys_pay, True)
|
||||
desc_change_pub = self.make_desc(pattern, privmap, keys_change, True)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc_pay)['descriptor'], desc_pay_pub)
|
||||
assert_equal(self.nodes[0].getdescriptorinfo(desc_change)['descriptor'], desc_change_pub)
|
||||
result = self.psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}])
|
||||
result = psbt_online.importdescriptors([{"desc": desc_pay_pub, "active": True, "timestamp": "now"}])
|
||||
assert(result[0]['success'])
|
||||
result = self.psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}])
|
||||
result = psbt_online.importdescriptors([{"desc": desc_change_pub, "active": True, "timestamp": "now", "internal": True}])
|
||||
assert(result[0]['success'])
|
||||
result = self.psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
|
||||
result = psbt_offline.importdescriptors([{"desc": desc_pay, "active": True, "timestamp": "now"}])
|
||||
assert(result[0]['success'])
|
||||
result = self.psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
|
||||
result = psbt_offline.importdescriptors([{"desc": desc_change, "active": True, "timestamp": "now", "internal": True}])
|
||||
assert(result[0]['success'])
|
||||
for key in keys_pay + keys_change:
|
||||
result = key_only_wallet.importdescriptors([{"desc": descsum_create(f"wpkh({key['xprv']}/*)"), "timestamp":"now"}])
|
||||
assert(result[0]["success"])
|
||||
address_type = "bech32m" if "tr" in pattern else "bech32"
|
||||
for i in range(4):
|
||||
addr_g = self.psbt_online.getnewaddress(address_type='bech32m')
|
||||
addr_g = psbt_online.getnewaddress(address_type=address_type)
|
||||
if treefn is not None:
|
||||
addr_r = self.make_addr(treefn, keys_pay, i)
|
||||
assert_equal(addr_g, addr_r)
|
||||
|
|
@ -302,52 +347,54 @@ class WalletTaprootTest(BitcoinTestFramework):
|
|||
to_amnt = random.randrange(1000000, boring_balance)
|
||||
self.boring.sendtoaddress(address=addr_g, amount=Decimal(to_amnt) / 100000000, subtractfeefromamount=True)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
test_balance = int(self.psbt_online.getbalance()['bitcoin'] * 100000000)
|
||||
test_balance = int(psbt_online.getbalance()['bitcoin'] * 100000000)
|
||||
ret_amnt = random.randrange(100000, test_balance)
|
||||
# Increase fee_rate to compensate for the wallet's inability to estimate fees for script path spends.
|
||||
psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": "bech32m"})['psbt']
|
||||
res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
psbt = psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): Decimal(ret_amnt) / 100000000}], None, {"subtractFeeFromOutputs":[0], "fee_rate": 200, "change_type": address_type})['psbt']
|
||||
res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
for wallet in [psbt_offline, key_only_wallet]:
|
||||
res = wallet.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
|
||||
decoded = self.psbt_offline.decodepsbt(res["psbt"])
|
||||
if pattern.startswith("tr("):
|
||||
for psbtin in decoded["inputs"]:
|
||||
assert "non_witness_utxo" not in psbtin
|
||||
assert "witness_utxo" in psbtin
|
||||
assert "taproot_internal_key" in psbtin
|
||||
assert "taproot_bip32_derivs" in psbtin
|
||||
assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin
|
||||
if "taproot_script_path_sigs" in psbtin:
|
||||
assert "taproot_merkle_root" in psbtin
|
||||
assert "taproot_scripts" in psbtin
|
||||
decoded = wallet.decodepsbt(res["psbt"])
|
||||
if pattern.startswith("tr("):
|
||||
for psbtin in decoded["inputs"]:
|
||||
assert "non_witness_utxo" not in psbtin
|
||||
assert "witness_utxo" in psbtin
|
||||
assert "taproot_internal_key" in psbtin
|
||||
assert "taproot_bip32_derivs" in psbtin
|
||||
assert "taproot_key_path_sig" in psbtin or "taproot_script_path_sigs" in psbtin
|
||||
if "taproot_script_path_sigs" in psbtin:
|
||||
assert "taproot_merkle_root" in psbtin
|
||||
assert "taproot_scripts" in psbtin
|
||||
|
||||
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
|
||||
res = self.nodes[0].testmempoolaccept([rawtx])
|
||||
assert res[0]["allowed"]
|
||||
|
||||
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
|
||||
txid = self.nodes[0].sendrawtransaction(rawtx)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0)
|
||||
assert(psbt_online.gettransaction(txid)['confirmations'] > 0)
|
||||
|
||||
# Cleanup
|
||||
psbt = psbt_online.sendall(recipients=[self.boring.getnewaddress()], options={"psbt": True})["psbt"]
|
||||
res = psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
|
||||
txid = self.nodes[0].sendrawtransaction(rawtx)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
assert(psbt_online.gettransaction(txid)['confirmations'] > 0)
|
||||
psbt_online.unloadwallet()
|
||||
psbt_offline.unloadwallet()
|
||||
|
||||
def do_test(self, comment, pattern, privmap, treefn):
|
||||
nkeys = len(privmap)
|
||||
keys = random.sample(KEYS, nkeys * 4)
|
||||
self.do_test_addr(comment, pattern, privmap, treefn, keys[0:nkeys])
|
||||
self.do_test_sendtoaddress(comment, pattern, privmap, treefn, keys[0:nkeys], keys[nkeys:2*nkeys])
|
||||
self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys])
|
||||
# self.do_test_psbt(comment, pattern, privmap, treefn, keys[2*nkeys:3*nkeys], keys[3*nkeys:4*nkeys]) # ELEMENTS FIXME: TX decode failed Output asset is required in PSET: iostream error
|
||||
|
||||
def run_test(self):
|
||||
self.log.info("Creating wallets...")
|
||||
self.nodes[0].createwallet(wallet_name="privs_tr_enabled", descriptors=True, blank=True)
|
||||
self.privs_tr_enabled = self.nodes[0].get_wallet_rpc("privs_tr_enabled")
|
||||
self.nodes[0].createwallet(wallet_name="pubs_tr_enabled", descriptors=True, blank=True, disable_private_keys=True)
|
||||
self.pubs_tr_enabled = self.nodes[0].get_wallet_rpc("pubs_tr_enabled")
|
||||
self.nodes[0].createwallet(wallet_name="boring")
|
||||
self.nodes[0].createwallet(wallet_name="addr_gen", descriptors=True, disable_private_keys=True, blank=True)
|
||||
self.nodes[0].createwallet(wallet_name="rpc_online", descriptors=True, blank=True)
|
||||
self.nodes[0].createwallet(wallet_name="psbt_online", descriptors=True, disable_private_keys=True, blank=True)
|
||||
self.nodes[1].createwallet(wallet_name="psbt_offline", descriptors=True, blank=True)
|
||||
self.boring = self.nodes[0].get_wallet_rpc("boring")
|
||||
self.addr_gen = self.nodes[0].get_wallet_rpc("addr_gen")
|
||||
self.rpc_online = self.nodes[0].get_wallet_rpc("rpc_online")
|
||||
self.psbt_online = self.nodes[0].get_wallet_rpc("psbt_online")
|
||||
self.psbt_offline = self.nodes[1].get_wallet_rpc("psbt_offline")
|
||||
|
||||
self.log.info("Mining blocks...")
|
||||
gen_addr = self.boring.getnewaddress()
|
||||
|
|
@ -457,18 +504,5 @@ class WalletTaprootTest(BitcoinTestFramework):
|
|||
lambda k1: key(k1)
|
||||
)
|
||||
|
||||
self.log.info("Sending everything back...")
|
||||
|
||||
txid = self.rpc_online.sendtoaddress(address=self.boring.getnewaddress(), amount=self.rpc_online.getbalance()['bitcoin'], subtractfeefromamount=True)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
assert(self.rpc_online.gettransaction(txid)["confirmations"] > 0)
|
||||
|
||||
psbt = self.psbt_online.walletcreatefundedpsbt([], [{self.boring.getnewaddress(): self.psbt_online.getbalance()['bitcoin']}], None, {"subtractFeeFromOutputs": [0]})['psbt']
|
||||
res = self.psbt_offline.walletprocesspsbt(psbt=psbt, finalize=False)
|
||||
rawtx = self.nodes[0].finalizepsbt(res['psbt'])['hex']
|
||||
txid = self.nodes[0].sendrawtransaction(rawtx)
|
||||
self.generatetoaddress(self.nodes[0], 1, self.boring.getnewaddress(), sync_fun=self.no_op)
|
||||
assert(self.psbt_online.gettransaction(txid)['confirmations'] > 0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
WalletTaprootTest().main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue