From 940a21932ba769ba5829cba713579db84f96d2f8 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Thu, 30 Nov 2017 16:49:11 -0800 Subject: [PATCH] SegWit wallet support This introduces two command line flags (-addresstype and -changetype) which control the type of addresses/outputs created by the GUI and RPCs. Certain RPCs allow overriding these (`getnewaddress` and `getrawchangeaddress`). Supported types are "legacy" (P2PKH and P2SH-multisig), "p2sh-segwit" (P2SH-P2WPKH and P2SH-P2WSH-multisig), and "bech32" (P2WPKH and P2WSH-multisig). A few utility functions are added to the wallet to construct different address type and to add the necessary entries to the wallet file to be compatible with earlier versions (see `CWallet::LearnRelatedScripts`, `GetDestinationForKey`, `GetAllDestinationsForKey`, `CWallet::AddAndGetDestinationForScript`). --- src/qt/addresstablemodel.cpp | 3 +- src/qt/paymentserver.cpp | 35 ++++--- src/qt/test/wallettests.cpp | 5 +- src/wallet/init.cpp | 12 +++ src/wallet/rpcdump.cpp | 12 ++- src/wallet/rpcwallet.cpp | 50 +++++++--- src/wallet/test/wallet_test_fixture.cpp | 2 + src/wallet/wallet.cpp | 117 +++++++++++++++++++++++- src/wallet/wallet.h | 45 +++++++++ test/functional/bip68-112-113-p2p.py | 2 +- test/functional/bip68-sequence.py | 3 +- test/functional/bumpfee.py | 3 +- test/functional/import-rescan.py | 2 +- test/functional/importmulti.py | 1 + test/functional/nulldummy.py | 2 +- test/functional/rawtransactions.py | 5 +- test/functional/segwit.py | 10 +- test/functional/signmessages.py | 1 + test/functional/test_runner.py | 1 + test/functional/txn_clone.py | 15 ++- test/functional/wallet-dump.py | 2 +- test/functional/wallet.py | 11 ++- 22 files changed, 278 insertions(+), 61 deletions(-) diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index 1d16940ac..f238c37f6 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -384,7 +384,8 @@ QString AddressTableModel::addRow(const QString &type, const QString &label, con return QString(); } } - strAddress = EncodeDestination(newKey.GetID()); + wallet->LearnRelatedScripts(newKey, g_address_type); + strAddress = EncodeDestination(GetDestinationForKey(newKey, g_address_type)); } else { diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index 510a3783a..48462f9b5 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -636,27 +636,24 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, const SendCoinsRecipient& r // Create a new refund address, or re-use: QString account = tr("Refund from %1").arg(recipient.authenticatedMerchant); std::string strAccount = account.toStdString(); - std::set refundAddresses = wallet->GetAccountAddresses(strAccount); - if (!refundAddresses.empty()) { - CScript s = GetScriptForDestination(*refundAddresses.begin()); + CPubKey newKey; + if (wallet->GetKeyFromPool(newKey)) { + // BIP70 requests encode the scriptPubKey directly, so we are not restricted to address + // types supported by the receiver. As a result, we choose the address format we also + // use for change. Despite an actual payment and not change, this is a close match: + // it's the output type we use subject to privacy issues, but not restricted by what + // other software supports. + wallet->LearnRelatedScripts(newKey, g_change_type); + CTxDestination dest = GetDestinationForKey(newKey, g_change_type); + wallet->SetAddressBook(dest, strAccount, "refund"); + + CScript s = GetScriptForDestination(dest); payments::Output* refund_to = payment.add_refund_to(); refund_to->set_script(&s[0], s.size()); - } - else { - CPubKey newKey; - if (wallet->GetKeyFromPool(newKey)) { - CKeyID keyID = newKey.GetID(); - wallet->SetAddressBook(keyID, strAccount, "refund"); - - CScript s = GetScriptForDestination(keyID); - payments::Output* refund_to = payment.add_refund_to(); - refund_to->set_script(&s[0], s.size()); - } - else { - // This should never happen, because sending coins should have - // just unlocked the wallet and refilled the keypool. - qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set"; - } + } else { + // This should never happen, because sending coins should have + // just unlocked the wallet and refilled the keypool. + qWarning() << "PaymentServer::fetchPaymentACK: Error getting refund key, refund_to not set"; } int length = payment.ByteSize(); diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 4b7c3bd72..a270e5de5 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -149,6 +149,9 @@ void BumpFee(TransactionView& view, const uint256& txid, bool expectDisabled, st // src/qt/test/test_bitcoin-qt -platform cocoa # macOS void TestGUI() { + g_address_type = OUTPUT_TYPE_P2SH_SEGWIT; + g_change_type = OUTPUT_TYPE_P2SH_SEGWIT; + // Set up wallet and chain with 105 blocks (5 mature blocks for spending). TestChain100Setup test; for (int i = 0; i < 5; ++i) { @@ -161,7 +164,7 @@ void TestGUI() wallet.LoadWallet(firstRun); { LOCK(wallet.cs_wallet); - wallet.SetAddressBook(test.coinbaseKey.GetPubKey().GetID(), "", "receive"); + wallet.SetAddressBook(GetDestinationForKey(test.coinbaseKey.GetPubKey(), g_address_type), "", "receive"); wallet.AddKeyPubKey(test.coinbaseKey, test.coinbaseKey.GetPubKey()); } { diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index 67c46df87..8da99f2f7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -16,6 +16,8 @@ std::string GetWalletHelpString(bool showDebug) { std::string strUsage = HelpMessageGroup(_("Wallet options:")); + strUsage += HelpMessageOpt("-addresstype", strprintf(_("What type of addresses to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default: \"%s\")"), FormatOutputType(OUTPUT_TYPE_DEFAULT))); + strUsage += HelpMessageOpt("-changetype", _("What type of change to use (\"legacy\", \"p2sh-segwit\", or \"bech32\", default is same as -addresstype)")); strUsage += HelpMessageOpt("-disablewallet", _("Do not load the wallet and disable wallet RPC calls")); strUsage += HelpMessageOpt("-keypool=", strprintf(_("Set key pool size to (default: %u)"), DEFAULT_KEYPOOL_SIZE)); strUsage += HelpMessageOpt("-fallbackfee=", strprintf(_("A fee rate (in %s/kB) that will be used when fee estimation has insufficient data (default: %s)"), @@ -175,6 +177,16 @@ bool WalletParameterInteraction() bSpendZeroConfChange = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); fWalletRbf = gArgs.GetBoolArg("-walletrbf", DEFAULT_WALLET_RBF); + g_address_type = ParseOutputType(gArgs.GetArg("-addresstype", "")); + if (g_address_type == OUTPUT_TYPE_NONE) { + return InitError(strprintf(_("Unknown address type '%s'"), gArgs.GetArg("-addresstype", ""))); + } + + g_change_type = ParseOutputType(gArgs.GetArg("-changetype", ""), g_address_type); + if (g_change_type == OUTPUT_TYPE_NONE) { + return InitError(strprintf(_("Unknown change type '%s'"), gArgs.GetArg("-changetype", ""))); + } + return true; } diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 81922c584..17d81a5e3 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -131,7 +131,11 @@ UniValue importprivkey(const JSONRPCRequest& request) CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); - pwallet->SetAddressBook(vchAddress, strLabel, "receive"); + + // We don't know which corresponding address will be used; label them all + for (const auto& dest : GetAllDestinationsForKey(pubkey)) { + pwallet->SetAddressBook(dest, strLabel, "receive"); + } // Don't throw error in case a key is already there if (pwallet->HaveKey(vchAddress)) { @@ -143,6 +147,7 @@ UniValue importprivkey(const JSONRPCRequest& request) if (!pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } + pwallet->LearnAllRelatedScripts(pubkey); // whenever a key is imported, we need to scan the whole chain pwallet->UpdateTimeFirstKey(1); @@ -433,8 +438,11 @@ UniValue importpubkey(const JSONRPCRequest& request) LOCK2(cs_main, pwallet->cs_wallet); - ImportAddress(pwallet, pubKey.GetID(), strLabel); + for (const auto& dest : GetAllDestinationsForKey(pubKey)) { + ImportAddress(pwallet, dest, strLabel); + } ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false); + pwallet->LearnAllRelatedScripts(pubKey); if (fRescan) { diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e4b2b020b..447dd794a 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -136,14 +136,15 @@ UniValue getnewaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 1) + if (request.fHelp || request.params.size() > 2) throw std::runtime_error( - "getnewaddress ( \"account\" )\n" + "getnewaddress ( \"account\" \"address_type\" )\n" "\nReturns a new Bitcoin address for receiving payments.\n" "If 'account' is specified (DEPRECATED), it is added to the address book \n" "so payments received with the address will be credited to 'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account name for the address to be linked to. If not provided, the default account \"\" is used. It can also be set to the empty string \"\" to represent the default account. The account does not need to exist, it will be created if there is no account by the given name.\n" + "2. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -addresstype.\n" "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" @@ -158,6 +159,14 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!request.params[0].isNull()) strAccount = AccountFromValue(request.params[0]); + OutputType output_type = g_address_type; + if (!request.params[1].isNull()) { + output_type = ParseOutputType(request.params[1].get_str(), g_address_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); + } + } + if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } @@ -167,11 +176,12 @@ UniValue getnewaddress(const JSONRPCRequest& request) if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } - CKeyID keyID = newKey.GetID(); + pwallet->LearnRelatedScripts(newKey, output_type); + CTxDestination dest = GetDestinationForKey(newKey, output_type); - pwallet->SetAddressBook(keyID, strAccount, "receive"); + pwallet->SetAddressBook(dest, strAccount, "receive"); - return EncodeDestination(keyID); + return EncodeDestination(dest); } @@ -226,11 +236,13 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) return NullUniValue; } - if (request.fHelp || request.params.size() > 0) + if (request.fHelp || request.params.size() > 1) throw std::runtime_error( - "getrawchangeaddress\n" + "getrawchangeaddress ( \"address_type\" )\n" "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n" + "\nArguments:\n" + "1. \"address_type\" (string, optional) The address type to use. Options are \"legacy\", \"p2sh\", and \"bech32\". Default is set by -changetype.\n" "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" @@ -244,6 +256,14 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) pwallet->TopUpKeyPool(); } + OutputType output_type = g_change_type; + if (!request.params[0].isNull()) { + output_type = ParseOutputType(request.params[0].get_str(), g_change_type); + if (output_type == OUTPUT_TYPE_NONE) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); + } + } + CReserveKey reservekey(pwallet); CPubKey vchPubKey; if (!reservekey.GetReservedKey(vchPubKey, true)) @@ -251,9 +271,10 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) reservekey.KeepKey(); - CKeyID keyID = vchPubKey.GetID(); + pwallet->LearnRelatedScripts(vchPubKey, output_type); + CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); - return EncodeDestination(keyID); + return EncodeDestination(dest); } @@ -1184,11 +1205,12 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) // Construct using pay-to-script-hash: CScript inner = _createmultisig_redeemScript(pwallet, request.params); - CScriptID innerID(inner); pwallet->AddCScript(inner); - pwallet->SetAddressBook(innerID, strAccount, "send"); - return EncodeDestination(innerID); + CTxDestination dest = pwallet->AddAndGetDestinationForScript(inner, g_address_type); + + pwallet->SetAddressBook(dest, strAccount, "send"); + return EncodeDestination(dest); } class Witnessifier : public boost::static_visitor @@ -3446,8 +3468,8 @@ static const CRPCCommand commands[] = { "wallet", "getaccount", &getaccount, {"address"} }, { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, {"account"} }, { "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, - { "wallet", "getnewaddress", &getnewaddress, {"account"} }, - { "wallet", "getrawchangeaddress", &getrawchangeaddress, {} }, + { "wallet", "getnewaddress", &getnewaddress, {"account","address_type"} }, + { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, {"account","minconf"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "gettransaction", &gettransaction, {"txid","include_watchonly"} }, diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp index 3ee83d2d7..86f4df600 100644 --- a/src/wallet/test/wallet_test_fixture.cpp +++ b/src/wallet/test/wallet_test_fixture.cpp @@ -13,6 +13,8 @@ WalletTestingSetup::WalletTestingSetup(const std::string& chainName): bitdb.MakeMock(); bool fFirstRun; + g_address_type = OUTPUT_TYPE_DEFAULT; + g_change_type = OUTPUT_TYPE_DEFAULT; std::unique_ptr dbw(new CWalletDBWrapper(&bitdb, "wallet_test.dat")); pwalletMain = MakeUnique(std::move(dbw)); pwalletMain->LoadWallet(fFirstRun); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index f73f78725..ff052c75b 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -42,6 +42,8 @@ CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE); unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET; bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE; bool fWalletRbf = DEFAULT_WALLET_RBF; +OutputType g_address_type = OUTPUT_TYPE_NONE; +OutputType g_change_type = OUTPUT_TYPE_NONE; const char * DEFAULT_WALLET_DAT = "wallet.dat"; const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; @@ -832,8 +834,8 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount if (!account.vchPubKey.IsValid()) bForceNew = true; else { - // Check if the current key has been used - CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); + // Check if the current key has been used (TODO: check other addresses with the same key) + CScript scriptPubKey = GetScriptForDestination(GetDestinationForKey(account.vchPubKey, g_address_type)); for (std::map::iterator it = mapWallet.begin(); it != mapWallet.end() && account.vchPubKey.IsValid(); ++it) @@ -850,11 +852,12 @@ bool CWallet::GetAccountDestination(CTxDestination &dest, std::string strAccount if (!GetKeyFromPool(account.vchPubKey, false)) return false; - dest = account.vchPubKey.GetID(); + LearnRelatedScripts(account.vchPubKey, g_address_type); + dest = GetDestinationForKey(account.vchPubKey, g_address_type); SetAddressBook(dest, strAccount, "receive"); walletdb.WriteAccount(strAccount, account); } else { - dest = account.vchPubKey.GetID(); + dest = GetDestinationForKey(account.vchPubKey, g_address_type); } return true; @@ -2743,7 +2746,8 @@ bool CWallet::CreateTransaction(const std::vector& vecSend, CWalletT return false; } - scriptChange = GetScriptForDestination(vchPubKey.GetID()); + LearnRelatedScripts(vchPubKey, g_change_type); + scriptChange = GetScriptForDestination(GetDestinationForKey(vchPubKey, g_change_type)); } CTxOut change_prototype_txout(0, scriptChange); size_t change_prototype_size = GetSerializeSize(change_prototype_txout, SER_DISK, 0); @@ -4136,3 +4140,106 @@ bool CWalletTx::AcceptToMemoryPool(const CAmount& nAbsurdFee, CValidationState& fInMempool = ret; return ret; } + +static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy"; +static const std::string OUTPUT_TYPE_STRING_P2SH_SEGWIT = "p2sh-segwit"; +static const std::string OUTPUT_TYPE_STRING_BECH32 = "bech32"; + +OutputType ParseOutputType(const std::string& type, OutputType default_type) +{ + if (type.empty()) { + return default_type; + } else if (type == OUTPUT_TYPE_STRING_LEGACY) { + return OUTPUT_TYPE_LEGACY; + } else if (type == OUTPUT_TYPE_STRING_P2SH_SEGWIT) { + return OUTPUT_TYPE_P2SH_SEGWIT; + } else if (type == OUTPUT_TYPE_STRING_BECH32) { + return OUTPUT_TYPE_BECH32; + } else { + return OUTPUT_TYPE_NONE; + } +} + +const std::string& FormatOutputType(OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return OUTPUT_TYPE_STRING_LEGACY; + case OUTPUT_TYPE_P2SH_SEGWIT: return OUTPUT_TYPE_STRING_P2SH_SEGWIT; + case OUTPUT_TYPE_BECH32: return OUTPUT_TYPE_STRING_BECH32; + default: assert(false); + } +} + +void CWallet::LearnRelatedScripts(const CPubKey& key, OutputType type) +{ + if (key.IsCompressed() && (type == OUTPUT_TYPE_P2SH_SEGWIT || type == OUTPUT_TYPE_BECH32)) { + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + // Make sure the resulting program is solvable. + assert(IsSolvable(*this, witprog)); + AddCScript(witprog); + } +} + +void CWallet::LearnAllRelatedScripts(const CPubKey& key) +{ + // OUTPUT_TYPE_P2SH_SEGWIT always adds all necessary scripts for all types. + LearnRelatedScripts(key, OUTPUT_TYPE_P2SH_SEGWIT); +} + +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType type) +{ + switch (type) { + case OUTPUT_TYPE_LEGACY: return key.GetID(); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + if (!key.IsCompressed()) return key.GetID(); + CTxDestination witdest = WitnessV0KeyHash(key.GetID()); + CScript witprog = GetScriptForDestination(witdest); + if (type == OUTPUT_TYPE_P2SH_SEGWIT) { + return CScriptID(witprog); + } else { + return witdest; + } + } + default: assert(false); + } +} + +std::vector GetAllDestinationsForKey(const CPubKey& key) +{ + CKeyID keyid = key.GetID(); + if (key.IsCompressed()) { + CTxDestination segwit = WitnessV0KeyHash(keyid); + CTxDestination p2sh = CScriptID(GetScriptForDestination(segwit)); + return std::vector{std::move(keyid), std::move(p2sh), std::move(segwit)}; + } else { + return std::vector{std::move(keyid)}; + } +} + +CTxDestination CWallet::AddAndGetDestinationForScript(const CScript& script, OutputType type) +{ + // Note that scripts over 520 bytes are not yet supported. + switch (type) { + case OUTPUT_TYPE_LEGACY: + return CScriptID(script); + case OUTPUT_TYPE_P2SH_SEGWIT: + case OUTPUT_TYPE_BECH32: { + WitnessV0ScriptHash hash; + CSHA256().Write(script.data(), script.size()).Finalize(hash.begin()); + CTxDestination witdest = hash; + CScript witprog = GetScriptForDestination(witdest); + // Check if the resulting program is solvable (i.e. doesn't use an uncompressed key) + if (!IsSolvable(*this, witprog)) return CScriptID(script); + // Add the redeemscript, so that P2WSH and P2SH-P2WSH outputs are recognized as ours. + AddCScript(witprog); + if (type == OUTPUT_TYPE_BECH32) { + return witdest; + } else { + return CScriptID(witprog); + } + } + default: assert(false); + } +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index e64ea2534..4088ed198 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -99,6 +99,19 @@ enum WalletFeature FEATURE_LATEST = FEATURE_COMPRPUBKEY // HD is optional, use FEATURE_COMPRPUBKEY as latest version }; +enum OutputType +{ + OUTPUT_TYPE_NONE, + OUTPUT_TYPE_LEGACY, + OUTPUT_TYPE_P2SH_SEGWIT, + OUTPUT_TYPE_BECH32, + + OUTPUT_TYPE_DEFAULT = OUTPUT_TYPE_P2SH_SEGWIT +}; + +extern OutputType g_address_type; +extern OutputType g_change_type; + /** A key pool entry */ class CKeyPool @@ -1129,6 +1142,26 @@ public: * deadlock */ void BlockUntilSyncedToCurrentChain(); + + /** + * Explicitly make the wallet learn the related scripts for outputs to the + * given key. This is purely to make the wallet file compatible with older + * software, as CBasicKeyStore automatically does this implicitly for all + * keys now. + */ + void LearnRelatedScripts(const CPubKey& key, OutputType); + + /** + * Same as LearnRelatedScripts, but when the OutputType is not known (and could + * be anything). + */ + void LearnAllRelatedScripts(const CPubKey& key); + + /** + * Get a destination of the requested type (if possible) to the specified script. + * This function will automatically add the necessary scripts to the wallet. + */ + CTxDestination AddAndGetDestinationForScript(const CScript& script, OutputType); }; /** A key allocated from the key pool. */ @@ -1218,4 +1251,16 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins return true; } +OutputType ParseOutputType(const std::string& str, OutputType default_type = OUTPUT_TYPE_DEFAULT); +const std::string& FormatOutputType(OutputType type); + +/** + * Get a destination of the requested type (if possible) to the specified key. + * The caller must make sure LearnRelatedScripts has been called beforehand. + */ +CTxDestination GetDestinationForKey(const CPubKey& key, OutputType); + +/** Get all destinations (potentially) supported by the wallet for the given key. */ +std::vector GetAllDestinationsForKey(const CPubKey& key); + #endif // BITCOIN_WALLET_WALLET_H diff --git a/test/functional/bip68-112-113-p2p.py b/test/functional/bip68-112-113-p2p.py index d3c7d8fc1..4cecfabc7 100755 --- a/test/functional/bip68-112-113-p2p.py +++ b/test/functional/bip68-112-113-p2p.py @@ -95,7 +95,7 @@ class BIP68_112_113Test(ComparisonTestFramework): def set_test_params(self): self.num_nodes = 1 self.setup_clean_chain = True - self.extra_args = [['-whitelist=127.0.0.1', '-blockversion=4']] + self.extra_args = [['-whitelist=127.0.0.1', '-blockversion=4', '-addresstype=legacy']] def run_test(self): test = TestManager(self, self.options.tmpdir) diff --git a/test/functional/bip68-sequence.py b/test/functional/bip68-sequence.py index 5f8f21701..146030542 100755 --- a/test/functional/bip68-sequence.py +++ b/test/functional/bip68-sequence.py @@ -362,9 +362,10 @@ class BIP68Test(BitcoinTestFramework): block.vtx.extend([tx1, tx2, tx3]) block.hashMerkleRoot = block.calc_merkle_root() block.rehash() + add_witness_commitment(block) block.solve() - self.nodes[0].submitblock(ToHex(block)) + self.nodes[0].submitblock(bytes_to_hex_str(block.serialize(True))) assert_equal(self.nodes[0].getbestblockhash(), block.hash) def activateCSV(self): diff --git a/test/functional/bumpfee.py b/test/functional/bumpfee.py index 008e83d5b..eb76801c5 100755 --- a/test/functional/bumpfee.py +++ b/test/functional/bumpfee.py @@ -194,7 +194,7 @@ def test_settxfee(rbf_node, dest_address): requested_feerate = Decimal("0.00025000") rbf_node.settxfee(requested_feerate) bumped_tx = rbf_node.bumpfee(rbfid) - actual_feerate = bumped_tx["fee"] * 1000 / rbf_node.getrawtransaction(bumped_tx["txid"], True)["size"] + actual_feerate = bumped_tx["fee"] * 1000 / rbf_node.getrawtransaction(bumped_tx["txid"], True)["vsize"] # Assert that the difference between the requested feerate and the actual # feerate of the bumped transaction is small. assert_greater_than(Decimal("0.00001000"), abs(requested_feerate - actual_feerate)) @@ -290,6 +290,7 @@ def submit_block_with_tx(node, tx): block.vtx.append(ctx) block.rehash() block.hashMerkleRoot = block.calc_merkle_root() + blocktools.add_witness_commitment(block) block.solve() node.submitblock(bytes_to_hex_str(block.serialize(True))) return block diff --git a/test/functional/import-rescan.py b/test/functional/import-rescan.py index 6807fa669..4306a8e22 100755 --- a/test/functional/import-rescan.py +++ b/test/functional/import-rescan.py @@ -119,7 +119,7 @@ class ImportRescanTest(BitcoinTestFramework): self.num_nodes = 2 + len(IMPORT_NODES) def setup_network(self): - extra_args = [[] for _ in range(self.num_nodes)] + extra_args = [["-addresstype=legacy"] for _ in range(self.num_nodes)] for i, import_node in enumerate(IMPORT_NODES, 2): if import_node.prune: extra_args[i] += ["-prune=1"] diff --git a/test/functional/importmulti.py b/test/functional/importmulti.py index a691595f1..922b82944 100755 --- a/test/functional/importmulti.py +++ b/test/functional/importmulti.py @@ -9,6 +9,7 @@ from test_framework.util import * class ImportMultiTest (BitcoinTestFramework): def set_test_params(self): self.num_nodes = 2 + self.extra_args = [["-addresstype=legacy"], ["-addresstype=legacy"]] self.setup_clean_chain = True def setup_network(self): diff --git a/test/functional/nulldummy.py b/test/functional/nulldummy.py index 9f9f2f90c..2426ebedb 100755 --- a/test/functional/nulldummy.py +++ b/test/functional/nulldummy.py @@ -42,7 +42,7 @@ class NULLDUMMYTest(BitcoinTestFramework): self.setup_clean_chain = True # This script tests NULLDUMMY activation, which is part of the 'segwit' deployment, so we go through # normal segwit activation here (and don't use the default always-on behaviour). - self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999']] + self.extra_args = [['-whitelist=127.0.0.1', '-walletprematurewitness', '-vbparams=segwit:0:999999999999', '-addresstype=legacy']] def run_test(self): self.address = self.nodes[0].getnewaddress() diff --git a/test/functional/rawtransactions.py b/test/functional/rawtransactions.py index 79f2a2834..1ff8d8a94 100755 --- a/test/functional/rawtransactions.py +++ b/test/functional/rawtransactions.py @@ -20,6 +20,7 @@ class RawTransactionsTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 3 + self.extra_args = [["-addresstype=legacy"], ["-addresstype=legacy"], ["-addresstype=legacy"]] def setup_network(self, split=False): super().setup_network() @@ -135,7 +136,7 @@ class RawTransactionsTest(BitcoinTestFramework): break bal = self.nodes[0].getbalance() - inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex']}] + inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "amount" : vout['value']}] outputs = { self.nodes[0].getnewaddress() : 2.19 } rawTx = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned = self.nodes[1].signrawtransaction(rawTx, inputs) @@ -180,7 +181,7 @@ class RawTransactionsTest(BitcoinTestFramework): break bal = self.nodes[0].getbalance() - inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex']}] + inputs = [{ "txid" : txId, "vout" : vout['n'], "scriptPubKey" : vout['scriptPubKey']['hex'], "redeemScript" : mSigObjValid['hex'], "amount" : vout['value']}] outputs = { self.nodes[0].getnewaddress() : 2.19 } rawTx2 = self.nodes[2].createrawtransaction(inputs, outputs) rawTxPartialSigned1 = self.nodes[1].signrawtransaction(rawTx2, inputs) diff --git a/test/functional/segwit.py b/test/functional/segwit.py index 626572336..f4b2e4dc5 100755 --- a/test/functional/segwit.py +++ b/test/functional/segwit.py @@ -78,9 +78,9 @@ class SegWitTest(BitcoinTestFramework): self.setup_clean_chain = True self.num_nodes = 3 # This test tests SegWit both pre and post-activation, so use the normal BIP9 activation. - self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999"], - ["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999"], - ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999"]] + self.extra_args = [["-walletprematurewitness", "-rpcserialversion=0", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"], + ["-blockversion=4", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-rpcserialversion=1", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"], + ["-blockversion=536870915", "-promiscuousmempoolflags=517", "-prematurewitness", "-walletprematurewitness", "-vbparams=segwit:0:999999999999", "-addresstype=legacy"]] def setup_network(self): super().setup_network() @@ -135,9 +135,9 @@ class SegWitTest(BitcoinTestFramework): self.pubkey.append(self.nodes[i].validateaddress(newaddress)["pubkey"]) multiaddress = self.nodes[i].addmultisigaddress(1, [self.pubkey[-1]]) multiscript = CScript([OP_1, hex_str_to_bytes(self.pubkey[-1]), OP_1, OP_CHECKMULTISIG]) - p2sh_addr = self.nodes[i].addwitnessaddress(newaddress, True) + p2sh_addr = self.nodes[i].addwitnessaddress(newaddress) bip173_addr = self.nodes[i].addwitnessaddress(newaddress, False) - p2sh_ms_addr = self.nodes[i].addwitnessaddress(multiaddress, True) + p2sh_ms_addr = self.nodes[i].addwitnessaddress(multiaddress) bip173_ms_addr = self.nodes[i].addwitnessaddress(multiaddress, False) assert_equal(p2sh_addr, key_to_p2sh_p2wpkh(self.pubkey[-1])) assert_equal(bip173_addr, key_to_p2wpkh(self.pubkey[-1])) diff --git a/test/functional/signmessages.py b/test/functional/signmessages.py index 52ba6a5ad..10449a7d6 100755 --- a/test/functional/signmessages.py +++ b/test/functional/signmessages.py @@ -11,6 +11,7 @@ class SignMessagesTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = True self.num_nodes = 1 + self.extra_args = [["-addresstype=legacy"]] def run_test(self): message = 'This is just a test message' diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 58faec521..6b83e862b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -86,6 +86,7 @@ BASE_SCRIPTS= [ 'mempool_resurrect_test.py', 'txn_doublespend.py --mineblock', 'txn_clone.py', + 'txn_clone.py --segwit', 'getchaintips.py', 'rest.py', 'mempool_spendcoinbase.py', diff --git a/test/functional/txn_clone.py b/test/functional/txn_clone.py index 740bb2d4c..20351625d 100755 --- a/test/functional/txn_clone.py +++ b/test/functional/txn_clone.py @@ -14,6 +14,8 @@ class TxnMallTest(BitcoinTestFramework): def add_options(self, parser): parser.add_option("--mineblock", dest="mine_block", default=False, action="store_true", help="Test double-spend of 1-confirmed transaction") + parser.add_option("--segwit", dest="segwit", default=False, action="store_true", + help="Test behaviour with SegWit txn (which should fail") def setup_network(self): # Start with split network: @@ -22,6 +24,11 @@ class TxnMallTest(BitcoinTestFramework): disconnect_nodes(self.nodes[2], 1) def run_test(self): + if self.options.segwit: + output_type="p2sh-segwit" + else: + output_type="legacy" + # All nodes should start with 1,250 BTC: starting_balance = 1250 for i in range(4): @@ -31,11 +38,11 @@ class TxnMallTest(BitcoinTestFramework): # Assign coins to foo and bar accounts: self.nodes[0].settxfee(.001) - node0_address_foo = self.nodes[0].getnewaddress("foo") + node0_address_foo = self.nodes[0].getnewaddress("foo", output_type) fund_foo_txid = self.nodes[0].sendfrom("", node0_address_foo, 1219) fund_foo_tx = self.nodes[0].gettransaction(fund_foo_txid) - node0_address_bar = self.nodes[0].getnewaddress("bar") + node0_address_bar = self.nodes[0].getnewaddress("bar", output_type) fund_bar_txid = self.nodes[0].sendfrom("", node0_address_bar, 29) fund_bar_tx = self.nodes[0].gettransaction(fund_bar_txid) @@ -106,6 +113,10 @@ class TxnMallTest(BitcoinTestFramework): # Send clone and its parent to miner self.nodes[2].sendrawtransaction(fund_foo_tx["hex"]) txid1_clone = self.nodes[2].sendrawtransaction(tx1_clone["hex"]) + if self.options.segwit: + assert_equal(txid1, txid1_clone) + return + # ... mine a block... self.nodes[2].generate(1) diff --git a/test/functional/wallet-dump.py b/test/functional/wallet-dump.py index 47de8777a..f1b7828e5 100755 --- a/test/functional/wallet-dump.py +++ b/test/functional/wallet-dump.py @@ -58,7 +58,7 @@ def read_dump(file_name, addrs, hd_master_addr_old): class WalletDumpTest(BitcoinTestFramework): def set_test_params(self): self.num_nodes = 1 - self.extra_args = [["-keypool=90"]] + self.extra_args = [["-keypool=90", "-addresstype=legacy"]] def setup_network(self, split=False): # Use 1 minute timeout because the initial getnewaddress RPC can take diff --git a/test/functional/wallet.py b/test/functional/wallet.py index db60df18e..68264c4c7 100755 --- a/test/functional/wallet.py +++ b/test/functional/wallet.py @@ -27,6 +27,9 @@ class WalletTest(BitcoinTestFramework): assert_fee_amount(fee, tx_size, fee_per_byte * 1000) return curr_balance + def get_vsize(self, txn): + return self.nodes[0].decoderawtransaction(txn)['vsize'] + def run_test(self): # Check that there's no UTXO on none of the nodes assert_equal(len(self.nodes[0].listunspent()), 0) @@ -162,7 +165,7 @@ class WalletTest(BitcoinTestFramework): txid = self.nodes[2].sendtoaddress(address, 10, "", "", False) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) - node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) + node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), Decimal('84'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) assert_equal(self.nodes[0].getbalance(), Decimal('10')) # Send 10 BTC with subtract fee from amount @@ -171,14 +174,14 @@ class WalletTest(BitcoinTestFramework): self.sync_all([self.nodes[0:3]]) node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) - node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) + node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), Decimal('20'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) # Sendmany 10 BTC txid = self.nodes[2].sendmany('from1', {address: 10}, 0, "", []) self.nodes[2].generate(1) self.sync_all([self.nodes[0:3]]) node_0_bal += Decimal('10') - node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) + node_2_bal = self.check_fee_amount(self.nodes[2].getbalance(), node_2_bal - Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) assert_equal(self.nodes[0].getbalance(), node_0_bal) # Sendmany 10 BTC with subtract fee from amount @@ -187,7 +190,7 @@ class WalletTest(BitcoinTestFramework): self.sync_all([self.nodes[0:3]]) node_2_bal -= Decimal('10') assert_equal(self.nodes[2].getbalance(), node_2_bal) - node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, count_bytes(self.nodes[2].getrawtransaction(txid))) + node_0_bal = self.check_fee_amount(self.nodes[0].getbalance(), node_0_bal + Decimal('10'), fee_per_byte, self.get_vsize(self.nodes[2].getrawtransaction(txid))) # Test ResendWalletTransactions: # Create a couple of transactions, then start up a fourth