diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index e4b6fea2b..b9e00960b 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1,2336 +1,2335 @@ // Copyright (c) 2009-2016 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 <chain.h> #include <config.h> #include <core_io.h> #include <interfaces/chain.h> #include <key_io.h> #include <merkleblock.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <script/script.h> #include <script/standard.h> #include <sync.h> #include <util/bip32.h> #include <util/system.h> #include <util/time.h> #include <util/translation.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <boost/algorithm/string.hpp> #include <cstdint> #include <tuple> using interfaces::FoundBlock; static std::string EncodeDumpString(const std::string &str) { std::stringstream ret; for (const uint8_t c : str) { if (c <= 32 || c >= 128 || c == '%') { ret << '%' << HexStr(&c, &c + 1); } else { ret << c; } } return ret.str(); } static std::string DecodeDumpString(const std::string &str) { std::stringstream ret; for (unsigned int pos = 0; pos < str.length(); pos++) { uint8_t c = str[pos]; if (c == '%' && pos + 2 < str.length()) { c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) | ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15)); pos += 2; } ret << c; } return ret.str(); } static bool GetWalletAddressesForKey(const Config &config, LegacyScriptPubKeyMan *spk_man, const CWallet *const pwallet, const CKeyID &keyid, std::string &strAddr, std::string &strLabel) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { bool fLabelFound = false; CKey key; spk_man->GetKey(keyid, key); for (const auto &dest : GetAllDestinationsForKey(key.GetPubKey())) { const auto *address_book_entry = pwallet->FindAddressBookEntry(dest); if (address_book_entry) { if (!strAddr.empty()) { strAddr += ","; } strAddr += EncodeDestination(dest, config); strLabel = EncodeDumpString(address_book_entry->GetLabel()); fLabelFound = true; } } if (!fLabelFound) { strAddr = EncodeDestination( GetDestinationForKey(key.GetPubKey(), pwallet->m_default_address_type), config); } return fLabelFound; } static const int64_t TIMESTAMP_MIN = 0; static void RescanWallet(CWallet &wallet, const WalletRescanReserver &reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true) { int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update); if (wallet.IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); } else if (scanned_time > time_begin) { throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. " "Some transactions may be missing."); } } UniValue importprivkey(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importprivkey", "Adds a private key (as returned by dumpprivkey) to your wallet. " "Requires a new wallet backup.\n" "Hint: use importmulti to import more than one private key.\n" "\nNote: This call can take minutes to complete if rescan is true, " "during that time, other rpc calls\n" "may report that the imported key exists but related transactions are " "still missing, leading to temporarily incorrect/bogus balances and " "unspent outputs until rescan completes.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"}, {"label", RPCArg::Type::STR, /* default */ "current label if address exists, otherwise \"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ "\nDump a private key\n" + HelpExampleCli("dumpprivkey", "\"myaddress\"") + "\nImport the private key with rescan\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nImport using a label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") + "\nImport using default blank label and without rescan\n" + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")}, } .Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with " "private keys disabled"); } EnsureLegacyScriptPubKeyMan(*wallet, true); WalletRescanReserver reserver(*pwallet); bool fRescan = true; { LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); std::string strSecret = request.params[0].get_str(); std::string strLabel = ""; if (!request.params[1].isNull()) { strLabel = request.params[1].get_str(); } // Whether to perform rescan after import if (!request.params[2].isNull()) { fRescan = request.params[2].get_bool(); } if (fRescan && pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), // but fail the rescan with a generic error. throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); } if (fRescan && !reserver.reserve()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing " "rescan or wait."); } CKey key = DecodeSecret(strSecret); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CPubKey pubkey = key.GetPubKey(); CHECK_NONFATAL(key.VerifyPubKey(pubkey)); CKeyID vchAddress = pubkey.GetID(); { pwallet->MarkDirty(); // We don't know which corresponding address will be used; // label all new addresses, and label existing addresses if a // label was passed. for (const auto &dest : GetAllDestinationsForKey(pubkey)) { if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(dest)) { pwallet->SetAddressBook(dest, strLabel, "receive"); } } // Use timestamp of 1 to scan the whole chain if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } } } if (fRescan) { RescanWallet(*pwallet, reserver); } return NullUniValue; } UniValue abortrescan(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "abortrescan", "Stops current wallet rescan triggered by an RPC call, e.g. by an " "importprivkey call.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", {}, RPCResult{RPCResult::Type::BOOL, "", "Whether the abort was successful"}, RPCExamples{"\nImport a private key\n" + HelpExampleCli("importprivkey", "\"mykey\"") + "\nAbort the running wallet rescan\n" + HelpExampleCli("abortrescan", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("abortrescan", "")}, } .Check(request); if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) { return false; } pwallet->AbortRescan(); return true; } UniValue importaddress(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importaddress", "Adds an address or script (in hex) that can be watched as if it " "were in your wallet but cannot be used to spend. Requires a new " "wallet backup.\n" "\nNote: This call can take minutes to complete if rescan is true, " "during that time, other rpc calls\n" "may report that the imported address exists but related transactions " "are still missing, leading to temporarily incorrect/bogus balances " "and unspent outputs until rescan completes.\n" "If you have the full public key, you should call importpubkey instead " "of this.\n" "Hint: use importmulti to import more than one address.\n" "\nNote: If you import a non-standard raw script in hex form, outputs " "sending to it will be treated\n" "as change, and not show up in many RPCs.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"}, {"label", RPCArg::Type::STR, /* default */ "\"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, {"p2sh", RPCArg::Type::BOOL, /* default */ "false", "Add the P2SH version of the script as well"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ "\nImport an address with rescan\n" + HelpExampleCli("importaddress", "\"myaddress\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")}, } .Check(request); EnsureLegacyScriptPubKeyMan(*pwallet, true); std::string strLabel; if (!request.params[1].isNull()) { strLabel = request.params[1].get_str(); } // Whether to perform rescan after import bool fRescan = true; if (!request.params[2].isNull()) { fRescan = request.params[2].get_bool(); } if (fRescan && pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), // but fail the rescan with a generic error. throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); } WalletRescanReserver reserver(*pwallet); if (fRescan && !reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } // Whether to import a p2sh version, too bool fP2SH = false; if (!request.params[3].isNull()) { fP2SH = request.params[3].get_bool(); } { LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str(), wallet->GetChainParams()); if (IsValidDestination(dest)) { if (fP2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - " "use a script instead"); } pwallet->MarkDirty(); pwallet->ImportScriptPubKeys( strLabel, {GetScriptForDestination(dest)}, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else if (IsHex(request.params[0].get_str())) { std::vector<uint8_t> data(ParseHex(request.params[0].get_str())); CScript redeem_script(data.begin(), data.end()); std::set<CScript> scripts = {redeem_script}; pwallet->ImportScripts(scripts, 0 /* timestamp */); if (fP2SH) { scripts.insert(GetScriptForDestination( ScriptHash(CScriptID(redeem_script)))); } pwallet->ImportScriptPubKeys( strLabel, scripts, false /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); } else { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address or script"); } } if (fRescan) { RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } } return NullUniValue; } UniValue importprunedfunds(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importprunedfunds", "Imports funds without rescan. Corresponding address or script must " "previously be included in wallet. Aimed towards pruned wallets. The " "end-user is responsible to import additional transactions that " "subsequently spend the imported outputs or rescan after the point in " "the blockchain the transaction is included.\n", { {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in " "wallet"}, {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{""}, } .Check(request); CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } uint256 txid = tx.GetId(); - CWalletTx wtx(pwallet, MakeTransactionRef(std::move(tx))); CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK, PROTOCOL_VERSION); CMerkleBlock merkleBlock; ssMB >> merkleBlock; // Search partial merkle tree in proof for our transaction and index in // valid block std::vector<uint256> vMatch; std::vector<size_t> vIndex; if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock"); } LOCK(pwallet->cs_wallet); int height; if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain"); } std::vector<uint256>::const_iterator it; if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) == vMatch.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof"); } size_t txnIndex = vIndex[it - vMatch.begin()]; CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, merkleBlock.header.GetHash(), txnIndex); - wtx.m_confirm = confirm; - if (pwallet->IsMine(*wtx.tx)) { - pwallet->AddToWallet(wtx, false); + CTransactionRef tx_ref = MakeTransactionRef(tx); + if (pwallet->IsMine(*tx_ref)) { + pwallet->AddToWallet(std::move(tx_ref), confirm); return NullUniValue; } throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction"); } UniValue removeprunedfunds(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "removeprunedfunds", "Deletes the specified transaction from the wallet. Meant for use " "with pruned wallets and as a companion to importprunedfunds. This " "will affect wallet balances.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf4644" "6a512166eae7628734ea0a5\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf4644" "6a512166eae7628734ea0a5\"")}, } .Check(request); LOCK(pwallet->cs_wallet); TxId txid(ParseHashV(request.params[0], "txid")); std::vector<TxId> txIds; txIds.push_back(txid); std::vector<TxId> txIdsOut; if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) { throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete the transaction."); } if (txIdsOut.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction does not exist in wallet."); } return NullUniValue; } UniValue importpubkey(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importpubkey", "Adds a public key (in hex) that can be watched as if it were in " "your wallet but cannot be used to spend. Requires a new wallet " "backup.\n" "Hint: use importmulti to import more than one public key.\n" "\nNote: This call can take minutes to complete if rescan is true, " "during that time, other rpc calls\n" "may report that the imported pubkey exists but related transactions " "are still missing, leading to temporarily incorrect/bogus balances " "and unspent outputs until rescan completes.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"}, {"label", RPCArg::Type::STR, /* default */ "\"\"", "An optional label"}, {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Rescan the wallet for transactions"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{ "\nImport a public key with rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\"") + "\nImport using a label without rescan\n" + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")}, } .Check(request); EnsureLegacyScriptPubKeyMan(*wallet, true); std::string strLabel; if (!request.params[1].isNull()) { strLabel = request.params[1].get_str(); } // Whether to perform rescan after import bool fRescan = true; if (!request.params[2].isNull()) { fRescan = request.params[2].get_bool(); } if (fRescan && pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), // but fail the rescan with a generic error. throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned"); } WalletRescanReserver reserver(*pwallet); if (fRescan && !reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } if (!IsHex(request.params[0].get_str())) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); } std::vector<uint8_t> data(ParseHex(request.params[0].get_str())); CPubKey pubKey(data.begin(), data.end()); if (!pubKey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); } { LOCK(pwallet->cs_wallet); std::set<CScript> script_pub_keys; for (const auto &dest : GetAllDestinationsForKey(pubKey)) { script_pub_keys.insert(GetScriptForDestination(dest)); } pwallet->MarkDirty(); pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, true /* have_solving_data */, true /* apply_label */, 1 /* timestamp */); pwallet->ImportPubKeys({pubKey.GetID()}, {{pubKey.GetID(), pubKey}}, {} /* key_origins */, false /* add_keypool */, false /* internal */, 1 /* timestamp */); } if (fRescan) { RescanWallet(*pwallet, reserver); { LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } } return NullUniValue; } UniValue importwallet(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importwallet", "Imports keys from a wallet dump file (see dumpwallet). Requires a " "new wallet backup to include imported keys.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"}, }, RPCResult{RPCResult::Type::NONE, "", ""}, RPCExamples{"\nDump the wallet\n" + HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" + HelpExampleCli("importwallet", "\"test\"") + "\nImport using the json rpc call\n" + HelpExampleRpc("importwallet", "\"test\"")}, } .Check(request); EnsureLegacyScriptPubKeyMan(*wallet, true); if (pwallet->chain().havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will import the key(s), // but fail the rescan with a generic error. throw JSONRPCError( RPC_WALLET_ERROR, "Importing wallets is disabled when blocks are pruned"); } WalletRescanReserver reserver(*pwallet); if (!reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } int64_t nTimeBegin = 0; bool fGood = true; { LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); fsbridge::ifstream file; file.open(request.params[0].get_str(), std::ios::in | std::ios::ate); if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } CHECK_NONFATAL(pwallet->chain().findBlock( pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin))); int64_t nFilesize = std::max<int64_t>(1, file.tellg()); file.seekg(0, file.beg); // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because // pwallet.ShowProgress has a cancel button tied to AbortRescan which we // don't want for this progress bar showing the import progress. // uiInterface.ShowProgress does not have a cancel button. // show progress dialog in GUI pwallet->chain().showProgress( strprintf("%s " + _("Importing...").translated, pwallet->GetDisplayName()), 0, false); std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys; std::vector<std::pair<CScript, int64_t>> scripts; while (file.good()) { pwallet->chain().showProgress( "", std::max(1, std::min<int>(50, 100 * double(file.tellg()) / double(nFilesize))), false); std::string line; std::getline(file, line); if (line.empty() || line[0] == '#') { continue; } std::vector<std::string> vstr; boost::split(vstr, line, boost::is_any_of(" ")); if (vstr.size() < 2) { continue; } CKey key = DecodeSecret(vstr[0]); if (key.IsValid()) { int64_t nTime = ParseISO8601DateTime(vstr[1]); std::string strLabel; bool fLabel = true; for (size_t nStr = 2; nStr < vstr.size(); nStr++) { if (vstr[nStr].front() == '#') { break; } if (vstr[nStr] == "change=1") { fLabel = false; } if (vstr[nStr] == "reserve=1") { fLabel = false; } if (vstr[nStr].substr(0, 6) == "label=") { strLabel = DecodeDumpString(vstr[nStr].substr(6)); fLabel = true; } } keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel)); } else if (IsHex(vstr[0])) { std::vector<uint8_t> vData(ParseHex(vstr[0])); CScript script = CScript(vData.begin(), vData.end()); int64_t birth_time = ParseISO8601DateTime(vstr[1]); scripts.push_back( std::pair<CScript, int64_t>(script, birth_time)); } } file.close(); // We now know whether we are importing private keys, so we can error if // private keys are disabled if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { // hide progress dialog in GUI pwallet->chain().showProgress("", 100, false); throw JSONRPCError( RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled"); } double total = double(keys.size() + scripts.size()); double progress = 0; for (const auto &key_tuple : keys) { pwallet->chain().showProgress( "", std::max(50, std::min<int>(75, 100 * progress / total) + 50), false); const CKey &key = std::get<0>(key_tuple); int64_t time = std::get<1>(key_tuple); bool has_label = std::get<2>(key_tuple); std::string label = std::get<3>(key_tuple); CPubKey pubkey = key.GetPubKey(); CHECK_NONFATAL(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid), config)); if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) { pwallet->WalletLogPrintf( "Error importing key for %s\n", EncodeDestination(PKHash(keyid), config)); fGood = false; continue; } if (has_label) { pwallet->SetAddressBook(PKHash(keyid), label, "receive"); } nTimeBegin = std::min(nTimeBegin, time); progress++; } for (const auto &script_pair : scripts) { pwallet->chain().showProgress( "", std::max(50, std::min<int>(75, 100 * progress / total) + 50), false); const CScript &script = script_pair.first; int64_t time = script_pair.second; if (!pwallet->ImportScripts({script}, time)) { pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script)); fGood = false; continue; } if (time > 0) { nTimeBegin = std::min(nTimeBegin, time); } progress++; } // hide progress dialog in GUI pwallet->chain().showProgress("", 100, false); } // hide progress dialog in GUI pwallet->chain().showProgress("", 100, false); RescanWallet(*pwallet, reserver, nTimeBegin, false /* update */); pwallet->MarkDirty(); if (!fGood) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet"); } return NullUniValue; } UniValue dumpprivkey(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); const CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } RPCHelpMan{ "dumpprivkey", "Reveals the private key corresponding to 'address'.\n" "Then the importprivkey can be used with this output\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for the private key"}, }, RPCResult{RPCResult::Type::STR, "key", "The private key"}, RPCExamples{HelpExampleCli("dumpprivkey", "\"myaddress\"") + HelpExampleCli("importprivkey", "\"mykey\"") + HelpExampleRpc("dumpprivkey", "\"myaddress\"")}, } .Check(request); LegacyScriptPubKeyMan &spk_man = EnsureLegacyScriptPubKeyMan(*wallet); LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore); EnsureWalletIsUnlocked(pwallet); std::string strAddress = request.params[0].get_str(); CTxDestination dest = DecodeDestination(strAddress, wallet->GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } auto keyid = GetKeyForDestination(spk_man, dest); if (keyid.IsNull()) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key"); } CKey vchSecret; if (!spk_man.GetKey(keyid, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } return EncodeSecret(vchSecret); } UniValue dumpwallet(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet.get(), request.fHelp)) { return NullUniValue; } RPCHelpMan{ "dumpwallet", "Dumps all wallet keys in a human-readable format to a server-side " "file. This does not allow overwriting existing files.\n" "Imported scripts are included in the dumpsfile, but corresponding " "addresses may not be added automatically by importwallet.\n" "Note that if your wallet contains keys which are not derived from " "your HD seed (e.g. imported keys), these are not covered by\n" "only backing up the seed itself, and must be backed up too (e.g. " "ensure you back up the whole dumpfile).\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"}, }, RPCResult{RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR, "filename", "The filename with full absolute path"}, }}, RPCExamples{HelpExampleCli("dumpwallet", "\"test\"") + HelpExampleRpc("dumpwallet", "\"test\"")}, } .Check(request); CWallet &wallet = *pwallet; LegacyScriptPubKeyMan &spk_man = EnsureLegacyScriptPubKeyMan(wallet); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); LOCK2(wallet.cs_wallet, spk_man.cs_KeyStore); EnsureWalletIsUnlocked(&wallet); fs::path filepath = request.params[0].get_str(); filepath = fs::absolute(filepath); /** * Prevent arbitrary files from being overwritten. There have been reports * that users have overwritten wallet files this way: * https://github.com/bitcoin/bitcoin/issues/9934 * It may also avoid other security issues. */ if (fs::exists(filepath)) { throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.string() + " already exists. If you are " "sure this is what you want, " "move it out of the way first"); } fsbridge::ofstream file; file.open(filepath); if (!file.is_open()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); } std::map<CKeyID, int64_t> mapKeyBirth; const std::map<CKeyID, int64_t> &mapKeyPool = spk_man.GetAllReserveKeys(); wallet.GetKeyBirthTimes(mapKeyBirth); std::set<CScriptID> scripts = spk_man.GetCScripts(); // sort time/key pairs std::vector<std::pair<int64_t, CKeyID>> vKeyBirth; for (const auto &entry : mapKeyBirth) { vKeyBirth.push_back(std::make_pair(entry.second, entry.first)); } mapKeyBirth.clear(); std::sort(vKeyBirth.begin(), vKeyBirth.end()); // produce output file << strprintf("# Wallet dump created by Bitcoin %s\n", CLIENT_BUILD); file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime())); file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString()); int64_t block_time = 0; CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time))); file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time)); file << "\n"; // add the base58check encoded extended master if the wallet uses HD CKeyID seed_id = spk_man.GetHDChain().seed_id; if (!seed_id.IsNull()) { CKey seed; if (spk_man.GetKey(seed_id, seed)) { CExtKey masterKey; masterKey.SetSeed(seed.begin(), seed.size()); file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n"; } } for (std::vector<std::pair<int64_t, CKeyID>>::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; std::string strTime = FormatISO8601DateTime(it->first); std::string strAddr; std::string strLabel; CKey key; if (spk_man.GetKey(keyid, key)) { file << strprintf("%s %s ", EncodeSecret(key), strTime); if (GetWalletAddressesForKey(config, &spk_man, &wallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == seed_id) { file << "hdseed=1"; } else if (mapKeyPool.count(keyid)) { file << "reserve=1"; } else if (spk_man.mapKeyMetadata[keyid].hdKeypath == "s") { file << "inactivehdseed=1"; } else { file << "change=1"; } file << strprintf( " # addr=%s%s\n", strAddr, (spk_man.mapKeyMetadata[keyid].has_key_origin ? " hdkeypath=" + WriteHDKeypath( spk_man.mapKeyMetadata[keyid].key_origin.path) : "")); } } file << "\n"; for (const CScriptID &scriptid : scripts) { CScript script; std::string create_time = "0"; std::string address = EncodeDestination(ScriptHash(scriptid), config); // get birth times for scripts with metadata auto it = spk_man.m_script_metadata.find(scriptid); if (it != spk_man.m_script_metadata.end()) { create_time = FormatISO8601DateTime(it->second.nCreateTime); } if (spk_man.GetCScript(scriptid, script)) { file << strprintf("%s %s script=1", HexStr(script.begin(), script.end()), create_time); file << strprintf(" # addr=%s\n", address); } } file << "\n"; file << "# End of dump\n"; file.close(); UniValue reply(UniValue::VOBJ); reply.pushKV("filename", filepath.string()); return reply; } static UniValue dumpcoins(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet.get(), request.fHelp)) { return NullUniValue; } RPCHelpMan{ "dumpcoins", "dump all the UTXO tracked by the wallet.\n", {}, RPCResult{ RPCResult::Type::OBJ_DYN, "", "", {{ RPCResult::Type::ARR, "address", "The list of UTXO corresponding to this address.", {{ RPCResult::Type::OBJ, "", "", { {RPCResult::Type::STR_HEX, "txid", "The transaction id"}, {RPCResult::Type::NUM, "vout", "The output number"}, {RPCResult::Type::NUM, "depth", "The output's depth"}, {RPCResult::Type::STR_AMOUNT, "value", "The output's amount"}, }, }}, }}, }, RPCExamples{HelpExampleCli("dumpcoins", "") + HelpExampleRpc("dumpcoins", "")}, } .Check(request); CWallet &wallet = *pwallet; // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); LOCK(wallet.cs_wallet); EnsureWalletIsUnlocked(&wallet); UniValue result(UniValue::VOBJ); for (const auto &p : wallet.ListCoins()) { UniValue coins(UniValue::VARR); for (const auto &o : p.second) { UniValue utxo(UniValue::VOBJ); utxo.pushKV("txid", o.tx->GetId().ToString()); utxo.pushKV("vout", o.i); utxo.pushKV("depth", o.nDepth); utxo.pushKV("value", ValueFromAmount(o.tx->tx->vout[o.i].nValue)); coins.push_back(std::move(utxo)); } result.pushKV(EncodeDestination(p.first, config), coins); } return result; } struct ImportData { // Input data //! Provided redeemScript; will be moved to `import_scripts` if relevant. std::unique_ptr<CScript> redeemscript; // Output data std::set<CScript> import_scripts; //! Import these private keys if available (the value indicates whether if //! the key is required for solvability) std::map<CKeyID, bool> used_keys; std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins; }; enum class ScriptContext { //! Top-level scriptPubKey TOP, //! P2SH redeemScript P2SH, }; // Analyse the provided scriptPubKey, determining which keys and which redeem // scripts from the ImportData struct are needed to spend it, and mark them as // used. Returns an error string, or the empty string for success. static std::string RecurseImportData(const CScript &script, ImportData &import_data, const ScriptContext script_ctx) { // Use Solver to obtain script type and parsed pubkeys or hashes: std::vector<std::vector<uint8_t>> solverdata; txnouttype script_type = Solver(script, solverdata); switch (script_type) { case TX_PUBKEY: { CPubKey pubkey(solverdata[0].begin(), solverdata[0].end()); import_data.used_keys.emplace(pubkey.GetID(), false); return ""; } case TX_PUBKEYHASH: { CKeyID id = CKeyID(uint160(solverdata[0])); import_data.used_keys[id] = true; return ""; } case TX_SCRIPTHASH: { if (script_ctx == ScriptContext::P2SH) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); } CHECK_NONFATAL(script_ctx == ScriptContext::TOP); CScriptID id = CScriptID(uint160(solverdata[0])); // Remove redeemscript from import_data to check for superfluous // script later. auto subscript = std::move(import_data.redeemscript); if (!subscript) { return "missing redeemscript"; } if (CScriptID(*subscript) != id) { return "redeemScript does not match the scriptPubKey"; } import_data.import_scripts.emplace(*subscript); return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); } case TX_MULTISIG: { for (size_t i = 1; i + 1 < solverdata.size(); ++i) { CPubKey pubkey(solverdata[i].begin(), solverdata[i].end()); import_data.used_keys.emplace(pubkey.GetID(), false); } return ""; } case TX_NULL_DATA: return "unspendable script"; case TX_NONSTANDARD: default: return "unrecognized script"; } } static UniValue ProcessImportLegacy( CWallet *const pwallet, ImportData &import_data, std::map<CKeyID, CPubKey> &pubkey_map, std::map<CKeyID, CKey> &privkey_map, std::set<CScript> &script_pub_keys, bool &have_solving_data, const UniValue &data, std::vector<CKeyID> &ordered_pubkeys) { UniValue warnings(UniValue::VARR); // First ensure scriptPubKey has either a script or JSON with "address" // string const UniValue &scriptPubKey = data["scriptPubKey"]; bool isScript = scriptPubKey.getType() == UniValue::VSTR; if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON " "with address string"); } const std::string &output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str(); // Optional fields. const std::string &strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : ""; const UniValue &pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue(); const UniValue &keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false; if (data.exists("range")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import"); } // Generate the script and destination for the scriptPubKey provided CScript script; if (!isScript) { CTxDestination dest = DecodeDestination(output, pwallet->GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); } std::vector<uint8_t> vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); CTxDestination dest; if (!ExtractDestination(script, dest) && !internal) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for " "nonstandard scriptPubKey imports."); } } script_pub_keys.emplace(script); // Parse all arguments if (strRedeemScript.size()) { if (!IsHex(strRedeemScript)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); } auto parsed_redeemscript = ParseHex(strRedeemScript); import_data.redeemscript = std::make_unique<CScript>( parsed_redeemscript.begin(), parsed_redeemscript.end()); } for (size_t i = 0; i < pubKeys.size(); ++i) { const auto &str = pubKeys[i].get_str(); if (!IsHex(str)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); } auto parsed_pubkey = ParseHex(str); CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); if (!pubkey.IsFullyValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); } pubkey_map.emplace(pubkey.GetID(), pubkey); ordered_pubkeys.push_back(pubkey.GetID()); } for (size_t i = 0; i < keys.size(); ++i) { const auto &str = keys[i].get_str(); CKey key = DecodeSecret(str); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CPubKey pubkey = key.GetPubKey(); CKeyID id = pubkey.GetID(); if (pubkey_map.count(id)) { pubkey_map.erase(id); } privkey_map.emplace(id, key); } // Verify and process input data have_solving_data = import_data.redeemscript || pubkey_map.size() || privkey_map.size(); if (have_solving_data) { // Match up data in import_data with the scriptPubKey in script. auto error = RecurseImportData(script, import_data, ScriptContext::TOP); // Verify whether the watchonly option corresponds to the // availability of private keys. bool spendable = std::all_of( import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool> &used_key) { return privkey_map.count(used_key.first) > 0; }); if (!watchOnly && !spendable) { warnings.push_back("Some private keys are missing, outputs " "will be considered watchonly. If this is " "intentional, specify the watchonly flag."); } if (watchOnly && spendable) { warnings.push_back( "All private keys are provided, outputs will be considered " "spendable. If this is intentional, do not specify the " "watchonly flag."); } // Check that all required keys for solvability are provided. if (error.empty()) { for (const auto &require_key : import_data.used_keys) { if (!require_key.second) { // Not a required key continue; } if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { error = "some required keys are missing"; } } } if (!error.empty()) { warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide " "any keys, pubkeys or redeemscript."); import_data = ImportData(); pubkey_map.clear(); privkey_map.clear(); have_solving_data = false; } else { // RecurseImportData() removes any relevant redeemscript from // import_data, so we can use that to discover if a superfluous // one was provided. if (import_data.redeemscript) { warnings.push_back( "Ignoring redeemscript as this is not a P2SH script."); } for (auto it = privkey_map.begin(); it != privkey_map.end();) { auto oldit = it++; if (import_data.used_keys.count(oldit->first) == 0) { warnings.push_back("Ignoring irrelevant private key."); privkey_map.erase(oldit); } } for (auto it = pubkey_map.begin(); it != pubkey_map.end();) { auto oldit = it++; auto key_data_it = import_data.used_keys.find(oldit->first); if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH."); pubkey_map.erase(oldit); } } } } return warnings; } static UniValue ProcessImportDescriptor(ImportData &import_data, std::map<CKeyID, CPubKey> &pubkey_map, std::map<CKeyID, CKey> &privkey_map, std::set<CScript> &script_pub_keys, bool &have_solving_data, const UniValue &data, std::vector<CKeyID> &ordered_pubkeys) { UniValue warnings(UniValue::VARR); const std::string &descriptor = data["desc"].get_str(); FlatSigningProvider keys; std::string error; auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } have_solving_data = parsed_desc->IsSolvable(); const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false; int64_t range_start = 0, range_end = 0; if (!parsed_desc->IsRange() && data.exists("range")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); } else if (parsed_desc->IsRange()) { if (!data.exists("range")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range"); } std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]); } const UniValue &priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue(); // Expand all descriptors to get public keys and scripts, and private keys // if available. for (int i = range_start; i <= range_end; ++i) { FlatSigningProvider out_keys; std::vector<CScript> scripts_temp; parsed_desc->Expand(i, keys, scripts_temp, out_keys); std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end())); for (const auto &key_pair : out_keys.pubkeys) { ordered_pubkeys.push_back(key_pair.first); } for (const auto &x : out_keys.scripts) { import_data.import_scripts.emplace(x.second); } parsed_desc->ExpandPrivate(i, keys, out_keys); std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end())); std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end())); import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end()); } for (size_t i = 0; i < priv_keys.size(); ++i) { const auto &str = priv_keys[i].get_str(); CKey key = DecodeSecret(str); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CPubKey pubkey = key.GetPubKey(); CKeyID id = pubkey.GetID(); // Check if this private key corresponds to a public key from the // descriptor if (!pubkey_map.count(id)) { warnings.push_back("Ignoring irrelevant private key."); } else { privkey_map.emplace(id, key); } } // Check if all the public keys have corresponding private keys in the // import for spendability. This does not take into account threshold // multisigs which could be spendable without all keys. Thus, threshold // multisigs without all keys will be considered not spendable here, even if // they are, perhaps triggering a false warning message. This is consistent // with the current wallet IsMine check. bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(), [&](const std::pair<CKeyID, CPubKey> &used_key) { return privkey_map.count(used_key.first) > 0; }) && std::all_of( import_data.key_origins.begin(), import_data.key_origins.end(), [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &entry) { return privkey_map.count(entry.first) > 0; }); if (!watch_only && !spendable) { warnings.push_back( "Some private keys are missing, outputs will be considered " "watchonly. If this is intentional, specify the watchonly flag."); } if (watch_only && spendable) { warnings.push_back("All private keys are provided, outputs will be " "considered spendable. If this is intentional, do " "not specify the watchonly flag."); } return warnings; } static UniValue ProcessImport(CWallet *const pwallet, const UniValue &data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); try { const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; // Internal addresses should not have a label if (internal && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } const std::string &label = data.exists("label") ? data["label"].get_str() : ""; const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false; // Add to keypool only works with privkeys disabled if (add_keypool && !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when " "private keys are disabled"); } ImportData import_data; std::map<CKeyID, CPubKey> pubkey_map; std::map<CKeyID, CKey> privkey_map; std::set<CScript> script_pub_keys; std::vector<CKeyID> ordered_pubkeys; bool have_solving_data; if (data.exists("scriptPubKey") && data.exists("desc")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided."); } else if (data.exists("scriptPubKey")) { warnings = ProcessImportLegacy( pwallet, import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); } else if (data.exists("desc")) { warnings = ProcessImportDescriptor( import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys); } else { throw JSONRPCError( RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided."); } // If private keys are disabled, abort if private keys are being // imported if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with " "private keys disabled"); } // Check whether we have any work to do for (const CScript &script : script_pub_keys) { if (pwallet->IsMine(script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private " "key for this address or script (\"" + HexStr(script.begin(), script.end()) + "\")"); } } // All good, time to import pwallet->MarkDirty(); if (!pwallet->ImportScripts(import_data.import_scripts, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } if (!pwallet->ImportPrivKeys(privkey_map, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } if (!pwallet->ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } if (!pwallet->ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); } result.pushKV("success", UniValue(true)); } catch (const UniValue &e) { result.pushKV("success", UniValue(false)); result.pushKV("error", e); } catch (...) { result.pushKV("success", UniValue(false)); result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); } if (warnings.size()) { result.pushKV("warnings", warnings); } return result; } static int64_t GetImportTimestamp(const UniValue &data, int64_t now) { if (data.exists("timestamp")) { const UniValue ×tamp = data["timestamp"]; if (timestamp.isNum()) { return timestamp.get_int64(); } else if (timestamp.isStr() && timestamp.get_str() == "now") { return now; } throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp " "value for key. got type %s", uvTypeName(timestamp.type()))); } throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key"); } UniValue importmulti(const Config &config, const JSONRPCRequest &mainRequest) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(mainRequest); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, mainRequest.fHelp)) { return NullUniValue; } RPCHelpMan{ "importmulti", "\nImport addresses/scripts (with private or public keys, redeem " "script (P2SH)), optionally rescanning the blockchain from the " "earliest creation time of the imported scripts. Requires a new wallet " "backup.\n" "If an address/script is imported without all of the private keys " "required to spend from that address, it will be watchonly. The " "'watchonly' option must be set to true in this case or a warning will " "be returned.\n" "Conversely, if all the private keys are provided and the " "address/script is spendable, the watchonly option must be set to " "false, or a warning will be returned.\n" "Note: Use \"getwalletinfo\" to query the scanning progress.\n", { {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not " "also provide address/scriptPubKey, scripts, or " "pubkeys"}, {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for " "address). Should not be provided if using a " "descriptor", /* oneline_description */ "", {"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}}, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n" " " " or the string \"now\" to " "substitute the current synced blockchain time. " "The " "timestamp of the oldest\n" " " " key will determine how far " "back " "blockchain rescans need to begin for missing " "wallet " "transactions.\n" " " " \"now\" can be specified to " "bypass scanning, for keys which are known to " "never " "have been used, and\n" " " " 0 can be specified to scan " "the " "entire blockchain. Blocks up to 2 hours before " "the " "earliest key\n" " " " creation time of all keys " "being " "imported by the importmulti call will be " "scanned.", /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}}, {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH " "address/scriptPubKey"}, {"pubkeys", RPCArg::Type::ARR, /* default */ "empty array", "Array of strings giving pubkeys to import. They " "must occur in P2PKH scripts. They are not required " "when the private key is also provided (see the " "\"keys\" argument).", { {"pubKey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, }}, {"keys", RPCArg::Type::ARR, /* default */ "empty array", "Array of strings giving private keys to import. The " "corresponding public keys must occur in the output " "or redeemscript.", { {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""}, }}, {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the " "end or the range (in the form [begin,end]) to " "import"}, {"internal", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be treated " "as not incoming payments (also known as change)"}, {"watchonly", RPCArg::Type::BOOL, /* default */ "false", "Stating whether matching outputs should be " "considered watchonly."}, {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with " "internal=false"}, {"keypool", RPCArg::Type::BOOL, /* default */ "false", "Stating whether imported public keys should be " "added to the keypool for when users request new " "addresses. Only allowed when wallet private keys " "are disabled"}, }, }, }, "\"requests\""}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { {"rescan", RPCArg::Type::BOOL, /* default */ "true", "Stating if should rescan the blockchain after all imports"}, }, "\"options\""}, }, RPCResult{RPCResult::Type::ARR, "", "Response is an array with the same size as the input that " "has the execution result", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "success", ""}, {RPCResult::Type::ARR, "warnings", /* optional */ true, "", { {RPCResult::Type::STR, "", ""}, }}, {RPCResult::Type::OBJ, "error", /* optional */ true, "", { {RPCResult::Type::ELISION, "", "JSONRPC error"}, }}, }}, }}, RPCExamples{ HelpExampleCli( "importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, " "\"timestamp\":1455191478 }, " "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" " "}, " "\"label\": \"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli( "importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, " "\"timestamp\":1455191478 }]' '{ \"rescan\": false}'") }, } .Check(mainRequest); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); EnsureLegacyScriptPubKeyMan(*wallet, true); const UniValue &requests = mainRequest.params[0]; // Default options bool fRescan = true; if (!mainRequest.params[1].isNull()) { const UniValue &options = mainRequest.params[1]; if (options.exists("rescan")) { fRescan = options["rescan"].get_bool(); } } WalletRescanReserver reserver(*pwallet); if (fRescan && !reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } int64_t now = 0; bool fRunScan = false; int64_t nLowestTimestamp = 0; UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); // Verify all timestamps are present before importing any keys. CHECK_NONFATAL(pwallet->chain().findBlock( pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now))); for (const UniValue &data : requests.getValues()) { GetImportTimestamp(data, now); } const int64_t minimumTimestamp = 1; for (const UniValue &data : requests.getValues()) { const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp); const UniValue result = ProcessImport(pwallet, data, timestamp); response.push_back(result); if (!fRescan) { continue; } // If at least one request was successful then allow rescan. if (result["success"].get_bool()) { fRunScan = true; } // Get the lowest timestamp. if (timestamp < nLowestTimestamp) { nLowestTimestamp = timestamp; } } } if (fRescan && fRunScan && requests.size()) { int64_t scannedTime = pwallet->RescanFromTime( nLowestTimestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); } if (scannedTime > nLowestTimestamp) { std::vector<UniValue> results = response.getValues(); response.clear(); response.setArray(); size_t i = 0; for (const UniValue &request : requests.getValues()) { // If key creation date is within the successfully scanned // range, or if the import result already has an error set, let // the result stand unmodified. Otherwise replace the result // with an error message. if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { response.push_back(results.at(i)); } else { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV( "error", JSONRPCError( RPC_MISC_ERROR, strprintf( "Rescan failed for key with creation timestamp " "%d. There was an error reading a block from " "time %d, which is after or within %d seconds " "of key creation, and could contain " "transactions pertaining to the key. As a " "result, transactions and coins using this key " "may not appear in the wallet. This error " "could be caused by pruning or data corruption " "(see bitcoind log for details) and could be " "dealt with by downloading and rescanning the " "relevant blocks (see -reindex and -rescan " "options).", GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); response.push_back(std::move(result)); } ++i; } } } return response; } static UniValue ProcessDescriptorImport(CWallet *const pwallet, const UniValue &data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { UniValue warnings(UniValue::VARR); UniValue result(UniValue::VOBJ); try { if (!data.exists("desc")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found."); } const std::string &descriptor = data["desc"].get_str(); const bool active = data.exists("active") ? data["active"].get_bool() : false; const bool internal = data.exists("internal") ? data["internal"].get_bool() : false; const std::string &label = data.exists("label") ? data["label"].get_str() : ""; // Parse descriptor string FlatSigningProvider keys; std::string error; auto parsed_desc = Parse(descriptor, keys, error, /* require_checksum = */ true); if (!parsed_desc) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error); } // Range check int64_t range_start = 0, range_end = 1, next_index = 0; if (!parsed_desc->IsRange() && data.exists("range")) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor"); } else if (parsed_desc->IsRange()) { if (data.exists("range")) { auto range = ParseDescriptorRange(data["range"]); range_start = range.first; // Specified range end is inclusive, but we need range end as // exclusive range_end = range.second + 1; } else { warnings.push_back( "Range not given, using default keypool range"); range_start = 0; range_end = gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE); } next_index = range_start; if (data.exists("next_index")) { next_index = data["next_index"].get_int64(); // bound checks if (next_index < range_start || next_index >= range_end) { throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range"); } } } // Active descriptors must be ranged if (active && !parsed_desc->IsRange()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged"); } // Ranged descriptors should not have a label if (data.exists("range") && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label"); } // Internal addresses should not have a label either if (internal && data.exists("label")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } // Combo descriptor check if (active && !parsed_desc->IsSingleType()) { throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active"); } // If the wallet disabled private keys, abort if private keys exist if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) { throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with " "private keys disabled"); } // Need to ExpandPrivate to check if private keys are available for all // pubkeys FlatSigningProvider expand_keys; std::vector<CScript> scripts; parsed_desc->Expand(0, keys, scripts, expand_keys); parsed_desc->ExpandPrivate(0, keys, expand_keys); // Check if all private keys are provided bool have_all_privkeys = !expand_keys.keys.empty(); for (const auto &entry : expand_keys.origins) { const CKeyID &key_id = entry.first; CKey key; if (!expand_keys.GetKey(key_id, key)) { have_all_privkeys = false; break; } } // If private keys are enabled, check some things. if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (keys.keys.empty()) { throw JSONRPCError( RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet " "with private keys enabled"); } if (!have_all_privkeys) { warnings.push_back( "Not all private keys provided. Some wallet functionality " "may return unexpected errors"); } } WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index); // Check if the wallet already contains the descriptor auto existing_spk_manager = pwallet->GetDescriptorScriptPubKeyMan(w_desc); if (existing_spk_manager) { LOCK(existing_spk_manager->cs_desc_man); if (range_start > existing_spk_manager->GetWalletDescriptor().range_start) { throw JSONRPCError( RPC_INVALID_PARAMS, strprintf( "range_start can only decrease; current range = " "[%d,%d]", existing_spk_manager->GetWalletDescriptor().range_start, existing_spk_manager->GetWalletDescriptor().range_end)); } } // Add descriptor to the wallet auto spk_manager = pwallet->AddWalletDescriptor(w_desc, keys, label); if (spk_manager == nullptr) { throw JSONRPCError( RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor)); } // Set descriptor as active if necessary if (active) { if (!w_desc.descriptor->GetOutputType()) { warnings.push_back( "Unknown output type, cannot set descriptor to active."); } else { pwallet->SetActiveScriptPubKeyMan( spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), internal); } } result.pushKV("success", UniValue(true)); } catch (const UniValue &e) { result.pushKV("success", UniValue(false)); result.pushKV("error", e); } catch (...) { result.pushKV("success", UniValue(false)); result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); } if (warnings.size()) { result.pushKV("warnings", warnings); } return result; } UniValue importdescriptors(const Config &config, const JSONRPCRequest &main_request) { // Acquire the wallet std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(main_request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, main_request.fHelp)) { return NullUniValue; } RPCHelpMan{ "importdescriptors", "\nImport descriptors. This will trigger a rescan of the blockchain " "based on the earliest timestamp of all descriptors being imported. " "Requires a new wallet backup.\n" "\nNote: This call can take over an hour to complete if using an early " "timestamp; during that time, other rpc calls\n" "may report that the imported keys, addresses or scripts exist but " "related transactions are still missing.\n", { {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."}, {"active", RPCArg::Type::BOOL, /* default */ "false", "Set this descriptor to be the active descriptor for " "the corresponding output type/externality"}, {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the " "end or the range (in the form [begin,end]) to " "import"}, {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this " "specifies the next index to generate addresses " "from"}, {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain " "for this descriptor, in " + UNIX_EPOCH_TIME + "\n" " " " Use the string \"now\" to " "substitute the current synced blockchain time.\n" " " " \"now\" can be specified to " "bypass scanning, for outputs which are known to " "never have been used, and\n" " " " 0 can be specified to scan the " "entire blockchain. Blocks up to 2 hours before " "the earliest timestamp\n" " " " of all descriptors being imported " "will be scanned.", /* oneline_description */ "", {"timestamp | \"now\"", "integer / string"}}, {"internal", RPCArg::Type::BOOL, /* default */ "false", "Whether matching outputs should be treated as not " "incoming payments (e.g. change)"}, {"label", RPCArg::Type::STR, /* default */ "''", "Label to assign to the address, only allowed with " "internal=false"}, }, }, }, "\"requests\""}, }, RPCResult{RPCResult::Type::ARR, "", "Response is an array with the same size as the input that " "has the execution result", { {RPCResult::Type::OBJ, "", "", { {RPCResult::Type::BOOL, "success", ""}, {RPCResult::Type::ARR, "warnings", /* optional */ true, "", { {RPCResult::Type::STR, "", ""}, }}, {RPCResult::Type::OBJ, "error", /* optional */ true, "", { {RPCResult::Type::ELISION, "", "JSONRPC error"}, }}, }}, }}, RPCExamples{ HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", " "\"timestamp\":1455191478, \"internal\": true }, " "{ \"desc\": \"<my desccriptor 2>\", \"label\": " "\"example 2\", \"timestamp\": 1455191480 }]'") + HelpExampleCli( "importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, " "\"active\": true, \"range\": [0,100], \"label\": \"<my " "cashaddr wallet>\" }]'")}, } .Check(main_request); // Make sure wallet is a descriptor wallet if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { throw JSONRPCError( RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets"); } RPCTypeCheck(main_request.params, {UniValue::VARR, UniValue::VOBJ}); WalletRescanReserver reserver(*pwallet); if (!reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } const UniValue &requests = main_request.params[0]; const int64_t minimum_timestamp = 1; int64_t now = 0; int64_t lowest_timestamp = 0; bool rescan = false; UniValue response(UniValue::VARR); { LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); CHECK_NONFATAL(pwallet->chain().findBlock( pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now))); // Get all timestamps and extract the lowest timestamp for (const UniValue &request : requests.getValues()) { // This throws an error if "timestamp" doesn't exist const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp); const UniValue result = ProcessDescriptorImport(pwallet, request, timestamp); response.push_back(result); if (lowest_timestamp > timestamp) { lowest_timestamp = timestamp; } // If we know the chain tip, and at least one request was successful // then allow rescan if (!rescan && result["success"].get_bool()) { rescan = true; } } pwallet->ConnectScriptPubKeyManNotifiers(); } // Rescan the blockchain using the lowest timestamp if (rescan) { int64_t scanned_time = pwallet->RescanFromTime( lowest_timestamp, reserver, true /* update */); { LOCK(pwallet->cs_wallet); pwallet->ReacceptWalletTransactions(); } if (pwallet->IsAbortingRescan()) { throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user."); } if (scanned_time > lowest_timestamp) { std::vector<UniValue> results = response.getValues(); response.clear(); response.setArray(); // Compose the response for (unsigned int i = 0; i < requests.size(); ++i) { const UniValue &request = requests.getValues().at(i); // If the descriptor timestamp is within the successfully // scanned range, or if the import result already has an error // set, let the result stand unmodified. Otherwise replace the // result with an error message. if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) { response.push_back(results.at(i)); } else { UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV( "error", JSONRPCError( RPC_MISC_ERROR, strprintf( "Rescan failed for descriptor with timestamp " "%d. There was an error reading a block from " "time %d, which is after or within %d seconds " "of key creation, and could contain " "transactions pertaining to the desc. As a " "result, transactions and coins using this " "desc may not appear in the wallet. This error " "could be caused by pruning or data corruption " "(see bitcoind log for details) and could be " "dealt with by downloading and rescanning the " "relevant blocks (see -reindex and -rescan " "options).", GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW))); response.push_back(std::move(result)); } } } } return response; } Span<const CRPCCommand> GetWalletDumpRPCCommands() { // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "wallet", "abortrescan", abortrescan, {} }, { "wallet", "dumpprivkey", dumpprivkey, {"address"} }, { "wallet", "dumpwallet", dumpwallet, {"filename"} }, { "wallet", "dumpcoins", dumpcoins, {} }, { "wallet", "importdescriptors", importdescriptors, {"requests"} }, { "wallet", "importmulti", importmulti, {"requests","options"} }, { "wallet", "importprivkey", importprivkey, {"privkey","label","rescan"} }, { "wallet", "importwallet", importwallet, {"filename"} }, { "wallet", "importaddress", importaddress, {"address","label","rescan","p2sh"} }, { "wallet", "importprunedfunds", importprunedfunds, {"rawtransaction","txoutproof"} }, { "wallet", "importpubkey", importpubkey, {"pubkey","label","rescan"} }, { "wallet", "removeprunedfunds", removeprunedfunds, {"txid"} }, }; // clang-format on return MakeSpan(commands); } diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index daf7b635d..a069e5ff7 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -1,832 +1,827 @@ // Copyright (c) 2017 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 <amount.h> #include <chainparams.h> // For Params #include <node/context.h> #include <primitives/transaction.h> #include <random.h> #include <wallet/coincontrol.h> #include <wallet/coinselection.h> #include <wallet/wallet.h> #include <test/util/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> #include <memory> #include <random> BOOST_FIXTURE_TEST_SUITE(coinselector_tests, WalletTestingSetup) // how many times to run all the tests to have a chance to catch errors that // only show up with particular random shuffles #define RUN_TESTS 100 // some tests fail 1% of the time due to bad luck. // we repeat those tests this many times and only complain if all iterations of // the test fail #define RANDOM_REPEATS 5 -std::vector<std::unique_ptr<CWalletTx>> wtxn; - typedef std::set<CInputCoin> CoinSet; static std::vector<COutput> vCoins; static NodeContext testNode; static Amount balance = Amount::zero(); CoinEligibilityFilter filter_standard(1, 6, 0); CoinEligibilityFilter filter_confirmed(1, 1, 0); CoinEligibilityFilter filter_standard_extra(6, 6, 0); CoinSelectionParams coin_selection_params(false, 0, 0, CFeeRate(Amount::zero()), 0); static void add_coin(const Amount nValue, int nInput, std::vector<CInputCoin> &set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; set.emplace_back(MakeTransactionRef(tx), nInput); } static void add_coin(const Amount nValue, int nInput, CoinSet &set) { CMutableTransaction tx; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; set.emplace(MakeTransactionRef(tx), nInput); } static void add_coin(CWallet &wallet, const Amount nValue, int nAge = 6 * 24, bool fIsFromMe = false, int nInput = 0, bool spendable = false) { balance += nValue; static int nextLockTime = 0; CMutableTransaction tx; // so all transactions get different hashes tx.nLockTime = nextLockTime++; tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; if (spendable) { CTxDestination dest; std::string error; assert(wallet.GetNewDestination(OutputType::LEGACY, "", dest, error)); tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest); } if (fIsFromMe) { // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if // vin.empty(), so stop vin being empty, and cache a non-zero Debit to // fake out IsFromMe() tx.vin.resize(1); } - auto wtx = - std::make_unique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx))); + CWalletTx *wtx = wallet.AddToWallet(MakeTransactionRef(std::move(tx)), + /* confirm= */ {}); if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, SATOSHI); wtx->m_is_cache_empty = false; } - COutput output(wtx.get(), nInput, nAge, true /* spendable */, - true /* solvable */, true /* safe */); + COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, + true /* safe */); vCoins.push_back(output); - wallet.AddToWallet(*wtx.get()); - wtxn.emplace_back(std::move(wtx)); } static void empty_wallet() { vCoins.clear(); - wtxn.clear(); balance = Amount::zero(); } static bool equal_sets(CoinSet a, CoinSet b) { std::pair<CoinSet::iterator, CoinSet::iterator> ret = mismatch(a.begin(), a.end(), b.begin()); return ret.first == a.end() && ret.second == b.end(); } static Amount make_hard_case(int utxos, std::vector<CInputCoin> &utxo_pool) { utxo_pool.clear(); Amount target = Amount::zero(); for (int i = 0; i < utxos; ++i) { const Amount base = (int64_t(1) << (utxos + i)) * SATOSHI; target += base; add_coin(base, 2 * i, utxo_pool); add_coin(base + (int64_t(1) << (utxos - 1 - i)) * SATOSHI, 2 * i + 1, utxo_pool); } return target; } inline std::vector<OutputGroup> & GroupCoins(const std::vector<CInputCoin> &coins) { static std::vector<OutputGroup> static_groups; static_groups.clear(); for (auto &coin : coins) { static_groups.emplace_back(coin, 0, true, 0, 0); } return static_groups; } inline std::vector<OutputGroup> &GroupCoins(const std::vector<COutput> &coins) { static std::vector<OutputGroup> static_groups; static_groups.clear(); for (auto &coin : coins) { // HACK: we can't figure out the is_me flag so we use the conditions // defined below; perhaps set safe to false for !fIsFromMe in add_coin() const bool is_me = coin.tx->m_amounts[CWalletTx::DEBIT].m_cached[ISMINE_SPENDABLE] && coin.tx->m_amounts[CWalletTx::DEBIT].m_value[ISMINE_SPENDABLE] == SATOSHI; static_groups.emplace_back(coin.GetInputCoin(), coin.nDepth, is_me, 0, 0); } return static_groups; } // Branch and bound coin selection tests BOOST_AUTO_TEST_CASE(bnb_search_test) { LOCK(m_wallet.cs_wallet); m_wallet.SetupLegacyScriptPubKeyMan(); // Setup std::vector<CInputCoin> utxo_pool; CoinSet selection; CoinSet actual_selection; Amount value_ret = Amount::zero(); Amount not_input_fees = Amount::zero(); ///////////////////////// // Known Outcome tests // ///////////////////////// // Empty utxo pool BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2, selection, value_ret, not_input_fees)); selection.clear(); // Add utxos add_coin(1 * CENT, 1, utxo_pool); add_coin(2 * CENT, 2, utxo_pool); add_coin(3 * CENT, 3, utxo_pool); add_coin(4 * CENT, 4, utxo_pool); // Select 1 Cent add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 1 * CENT); actual_selection.clear(); selection.clear(); // Select 2 Cent add_coin(2 * CENT, 2, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 2 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 2 * CENT); actual_selection.clear(); selection.clear(); // Select 5 Cent add_coin(4 * CENT, 4, actual_selection); add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 5 * CENT); actual_selection.clear(); selection.clear(); // Select 11 Cent, not possible BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 11 * CENT, CENT / 2, selection, value_ret, not_input_fees)); actual_selection.clear(); selection.clear(); // Cost of change is greater than the difference between target value and // utxo sum add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 9 * CENT / 10, 5 * CENT / 10, selection, value_ret, not_input_fees)); BOOST_CHECK_EQUAL(value_ret, 1 * CENT); BOOST_CHECK(equal_sets(selection, actual_selection)); actual_selection.clear(); selection.clear(); // Cost of change is less than the difference between target value and utxo // sum BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 9 * CENT / 10, Amount::zero(), selection, value_ret, not_input_fees)); actual_selection.clear(); selection.clear(); // Select 10 Cent add_coin(5 * CENT, 5, utxo_pool); add_coin(5 * CENT, 5, actual_selection); add_coin(4 * CENT, 4, actual_selection); add_coin(1 * CENT, 1, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, CENT / 2, selection, value_ret, not_input_fees)); BOOST_CHECK(equal_sets(selection, actual_selection)); BOOST_CHECK_EQUAL(value_ret, 10 * CENT); actual_selection.clear(); selection.clear(); // Negative effective value // Select 10 Cent but have 1 Cent not be possible because too small add_coin(5 * CENT, 5, actual_selection); add_coin(3 * CENT, 3, actual_selection); add_coin(2 * CENT, 2, actual_selection); BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 5000 * SATOSHI, selection, value_ret, not_input_fees)); BOOST_CHECK_EQUAL(value_ret, 10 * CENT); // FIXME: this test is redundant with the above, because 1 Cent is selected, // not "too small" BOOST_CHECK(equal_sets(selection, actual_selection)); // Select 0.25 Cent, not possible BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), CENT / 4, CENT / 2, selection, value_ret, not_input_fees)); actual_selection.clear(); selection.clear(); // Iteration exhaustion test Amount target = make_hard_case(17, utxo_pool); // Should exhaust BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(), selection, value_ret, not_input_fees)); target = make_hard_case(14, utxo_pool); // Should not exhaust BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), target, Amount::zero(), selection, value_ret, not_input_fees)); // Test same value early bailout optimization utxo_pool.clear(); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, actual_selection); add_coin(2 * CENT, 7, actual_selection); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(7 * CENT, 7, utxo_pool); add_coin(2 * CENT, 7, utxo_pool); for (int i = 0; i < 50000; ++i) { add_coin(5 * CENT, 7, utxo_pool); } BOOST_CHECK(SelectCoinsBnB(GroupCoins(utxo_pool), 30 * CENT, 5000 * SATOSHI, selection, value_ret, not_input_fees)); BOOST_CHECK_EQUAL(value_ret, 30 * CENT); BOOST_CHECK(equal_sets(selection, actual_selection)); //////////////////// // Behavior tests // //////////////////// // Select 1 Cent with pool of only greater than 5 Cent utxo_pool.clear(); for (int i = 5; i <= 20; ++i) { add_coin(i * CENT, i, utxo_pool); } // Run 100 times, to make sure it is never finding a solution for (int i = 0; i < 100; ++i) { BOOST_CHECK(!SelectCoinsBnB(GroupCoins(utxo_pool), 1 * CENT, 2 * CENT, selection, value_ret, not_input_fees)); } // Make sure that effective value is working in SelectCoinsMinConf when BnB // is used CoinSelectionParams coin_selection_params_bnb(true, 0, 0, CFeeRate(3000 * SATOSHI), 0); CoinSet setCoinsRet; Amount nValueRet; bool bnb_used; empty_wallet(); add_coin(m_wallet, SATOSHI); // Make sure that it has a negative effective value. The next check should // assert if this somehow got through. Otherwise it will fail vCoins.at(0).nInputBytes = 40; BOOST_CHECK(!m_wallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); // Test fees subtracted from output: empty_wallet(); add_coin(m_wallet, 1 * CENT); vCoins.at(0).nInputBytes = 40; BOOST_CHECK(!m_wallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); coin_selection_params_bnb.m_subtract_fee_outputs = true; BOOST_CHECK(m_wallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // Make sure that can use BnB when there are preset inputs empty_wallet(); { auto wallet = std::make_unique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); bool firstRun; wallet->LoadWallet(firstRun); LOCK(wallet->cs_wallet); wallet->SetupLegacyScriptPubKeyMan(); add_coin(*wallet, 5 * CENT, 6 * 24, false, 0, true); add_coin(*wallet, 3 * CENT, 6 * 24, false, 0, true); add_coin(*wallet, 2 * CENT, 6 * 24, false, 0, true); CCoinControl coin_control; coin_control.fAllowOtherInputs = true; coin_control.Select( COutPoint(vCoins.at(0).tx->GetId(), vCoins.at(0).i)); coin_selection_params_bnb.effective_fee = CFeeRate(Amount::zero()); BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb, bnb_used)); BOOST_CHECK(bnb_used); BOOST_CHECK(coin_selection_params_bnb.use_bnb); } } BOOST_AUTO_TEST_CASE(knapsack_solver_test) { auto testChain = interfaces::MakeChain(testNode, Params()); CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); CoinSet setCoinsRet, setCoinsRet2; Amount nValueRet; bool bnb_used; LOCK(testWallet.cs_wallet); testWallet.SetupLegacyScriptPubKeyMan(); // test multiple times to allow for differences in the shuffle order for (int i = 0; i < RUN_TESTS; i++) { empty_wallet(); // with an empty wallet we can't even pay one cent BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // add a new 1 cent coin add_coin(testWallet, 1 * CENT, 4); // with a new 1 cent coin, we still can't find a mature 1 cent BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but we can find a new 1 cent BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // add a mature 2 cent coin add_coin(testWallet, 2 * CENT); // we can't make 3 cents of mature coins BOOST_CHECK(!testWallet.SelectCoinsMinConf( 3 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we can make 3 cents of new coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 3 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 3 * CENT); // add a mature 5 cent coin, add_coin(testWallet, 5 * CENT); // a new 10 cent coin sent from one of our own addresses add_coin(testWallet, 10 * CENT, 3, true); // and a mature 20 cent coin add_coin(testWallet, 20 * CENT); // now we have new: 1+10=11 (of which 10 was self-sent), and mature: // 2+5+20=27. total = 38 // we can't make 38 cents only if we disallow new coins: BOOST_CHECK(!testWallet.SelectCoinsMinConf( 38 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we can't even make 37 cents if we don't allow new coins even if // they're from us BOOST_CHECK(!testWallet.SelectCoinsMinConf( 38 * CENT, filter_standard_extra, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but we can make 37 cents if we accept new coins from ourself BOOST_CHECK(testWallet.SelectCoinsMinConf( 37 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 37 * CENT); // and we can make 38 cents if we accept all new coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 38 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 38 * CENT); // try making 34 cents from 1,2,5,10,20 - we can't do it exactly BOOST_CHECK(testWallet.SelectCoinsMinConf( 34 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // but 35 cents is closest BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got // included (but possible) BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // when we try making 7 cents, the smaller coins (1,2,5) are enough. We // should see just 2+5 BOOST_CHECK(testWallet.SelectCoinsMinConf( 7 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 7 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // when we try making 8 cents, the smaller coins (1,2,5) are exactly // enough. BOOST_CHECK(testWallet.SelectCoinsMinConf( 8 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(nValueRet == 8 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // when we try making 9 cents, no subset of smaller coins is enough, and // we get the next bigger coin (10) BOOST_CHECK(testWallet.SelectCoinsMinConf( 9 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 10 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now clear out the wallet and start again to test choosing between // subsets of smaller coins and the next biggest coin empty_wallet(); add_coin(testWallet, 6 * CENT); add_coin(testWallet, 7 * CENT); add_coin(testWallet, 8 * CENT); add_coin(testWallet, 20 * CENT); // now we have 6+7+8+20+30 = 71 cents total add_coin(testWallet, 30 * CENT); // check that we have 71 and not 72 BOOST_CHECK(testWallet.SelectCoinsMinConf( 71 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(!testWallet.SelectCoinsMinConf( 72 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // now try making 16 cents. the best smaller coins can do is 6+7+8 = // 21; not as good at the next biggest coin, 20 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 20 in one coin BOOST_CHECK_EQUAL(nValueRet, 20 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now we have 5+6+7+8+20+30 = 75 cents total add_coin(testWallet, 5 * CENT); // now if we try making 16 cents again, the smaller coins can make 5+6+7 // = 18 cents, better than the next biggest coin, 20 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 18 in 3 coins BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // now we have 5+6+7+8+18+20+30 add_coin(testWallet, 18 * CENT); // and now if we try making 16 cents again, the smaller coins can make // 5+6+7 = 18 cents, the same as the next biggest coin, 18 BOOST_CHECK(testWallet.SelectCoinsMinConf( 16 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 18 in 1 coin BOOST_CHECK_EQUAL(nValueRet, 18 * CENT); // because in the event of a tie, the biggest coin wins BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // now try making 11 cents. we should get 5+6 BOOST_CHECK(testWallet.SelectCoinsMinConf( 11 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 11 * CENT); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // check that the smallest bigger coin is used add_coin(testWallet, 1 * COIN); add_coin(testWallet, 2 * COIN); add_coin(testWallet, 3 * COIN); // now we have 5+6+7+8+18+20+30+100+200+300+400 = 1094 cents add_coin(testWallet, 4 * COIN); BOOST_CHECK(testWallet.SelectCoinsMinConf( 95 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 1 BCH in 1 coin BOOST_CHECK_EQUAL(nValueRet, 1 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); BOOST_CHECK(testWallet.SelectCoinsMinConf( 195 * CENT, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get 2 BCH in 1 coin BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // empty the wallet and start again, now with fractions of a cent, to // test small change avoidance empty_wallet(); add_coin(testWallet, 1 * MIN_CHANGE / 10); add_coin(testWallet, 2 * MIN_CHANGE / 10); add_coin(testWallet, 3 * MIN_CHANGE / 10); add_coin(testWallet, 4 * MIN_CHANGE / 10); add_coin(testWallet, 5 * MIN_CHANGE / 10); // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE // we'll get change smaller than MIN_CHANGE whatever happens, so can // expect MIN_CHANGE exactly BOOST_CHECK(testWallet.SelectCoinsMinConf( MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // but if we add a bigger coin, small change is avoided add_coin(testWallet, 1111 * MIN_CHANGE); // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // if we add more small coins: add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 7 * MIN_CHANGE / 10); // and try again to make 1.0 * MIN_CHANGE BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // run the 'mtgox' test (see // http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf) // they tried to consolidate 10 50k coins into one 500k coin, and ended // up with 50k in change empty_wallet(); for (int j = 0; j < 20; j++) { add_coin(testWallet, 50000 * COIN); } BOOST_CHECK(testWallet.SelectCoinsMinConf( 500000 * COIN, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // in ten coins BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // if there's not enough in the smaller coins to make at least 1 * // MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0), we need to try finding an // exact subset anyway // sometimes it will fail, and so we use the next biggest coin: empty_wallet(); add_coin(testWallet, 5 * MIN_CHANGE / 10); add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 7 * MIN_CHANGE / 10); add_coin(testWallet, 1111 * MIN_CHANGE); BOOST_CHECK(testWallet.SelectCoinsMinConf( 1 * MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we get the bigger coin BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); // but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = // 1.0) empty_wallet(); add_coin(testWallet, 4 * MIN_CHANGE / 10); add_coin(testWallet, 6 * MIN_CHANGE / 10); add_coin(testWallet, 8 * MIN_CHANGE / 10); add_coin(testWallet, 1111 * MIN_CHANGE); BOOST_CHECK(testWallet.SelectCoinsMinConf( MIN_CHANGE, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get the exact amount BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // in two coins 0.4+0.6 BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // test avoiding small change empty_wallet(); add_coin(testWallet, 5 * MIN_CHANGE / 100); add_coin(testWallet, 1 * MIN_CHANGE); add_coin(testWallet, 100 * MIN_CHANGE); // trying to make 100.01 from these three coins BOOST_CHECK(testWallet.SelectCoinsMinConf( 10001 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // we should get all coins BOOST_CHECK_EQUAL(nValueRet, 10105 * MIN_CHANGE / 100); BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // but if we try to make 99.9, we should take the bigger of the two // small coins to avoid small change BOOST_CHECK(testWallet.SelectCoinsMinConf( 9990 * MIN_CHANGE / 100, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); } // test with many inputs for (Amount amt = 1500 * SATOSHI; amt < COIN; amt = 10 * amt) { empty_wallet(); // Create 676 inputs (= (old MAX_STANDARD_TX_SIZE == 100000) / 148 // bytes per input) for (uint16_t j = 0; j < 676; j++) { add_coin(testWallet, amt); } // We only create the wallet once to save time, but we still run the // coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { BOOST_CHECK(testWallet.SelectCoinsMinConf( 2000 * SATOSHI, filter_confirmed, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); if (amt - 2000 * SATOSHI < MIN_CHANGE) { // needs more than one input: uint16_t returnSize = std::ceil( (2000.0 + (MIN_CHANGE / SATOSHI)) / (amt / SATOSHI)); Amount returnValue = returnSize * amt; BOOST_CHECK_EQUAL(nValueRet, returnValue); BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize); } else { // one input is sufficient: BOOST_CHECK_EQUAL(nValueRet, amt); BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U); } } } // test randomness { empty_wallet(); for (int i2 = 0; i2 < 100; i2++) { add_coin(testWallet, COIN); } // Again, we only create the wallet once to save time, but we still run // the coin selection RUN_TESTS times. for (int i = 0; i < RUN_TESTS; i++) { // picking 50 from 100 coins doesn't depend on the shuffle, but does // depend on randomness in the stochastic approximation code BOOST_CHECK(testWallet.SelectCoinsMinConf( 50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( 50 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(!equal_sets(setCoinsRet, setCoinsRet2)); int fails = 0; for (int j = 0; j < RANDOM_REPEATS; j++) { // selecting 1 from 100 identical coins depends on the shuffle; // this test will fail 1% of the time run the test // RANDOM_REPEATS times and only complain if all of them fail BOOST_CHECK(testWallet.SelectCoinsMinConf( COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( COIN, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); if (equal_sets(setCoinsRet, setCoinsRet2)) { fails++; } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); } // add 75 cents in small change. not enough to make 90 cents, then // try making 90 cents. there are multiple competing "smallest // bigger" coins, one of which should be picked at random add_coin(testWallet, 5 * CENT); add_coin(testWallet, 10 * CENT); add_coin(testWallet, 15 * CENT); add_coin(testWallet, 20 * CENT); add_coin(testWallet, 25 * CENT); for (int i = 0; i < RUN_TESTS; i++) { int fails = 0; for (int j = 0; j < RANDOM_REPEATS; j++) { // selecting 1 from 100 identical coins depends on the shuffle; // this test will fail 1% of the time run the test // RANDOM_REPEATS times and only complain if all of them fail BOOST_CHECK(testWallet.SelectCoinsMinConf( 90 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK(testWallet.SelectCoinsMinConf( 90 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet2, nValueRet, coin_selection_params, bnb_used)); if (equal_sets(setCoinsRet, setCoinsRet2)) { fails++; } } BOOST_CHECK_NE(fails, RANDOM_REPEATS); } } empty_wallet(); } BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { CoinSet setCoinsRet; Amount nValueRet; bool bnb_used; LOCK(m_wallet.cs_wallet); m_wallet.SetupLegacyScriptPubKeyMan(); empty_wallet(); // Test vValue sort order for (int i = 0; i < 1000; i++) { add_coin(m_wallet, 1000 * COIN); } add_coin(m_wallet, 3 * COIN); BOOST_CHECK(m_wallet.SelectCoinsMinConf( 1003 * COIN, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params, bnb_used)); BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); empty_wallet(); } // Tests that with the ideal conditions, the coin selector will always be able // to find a solution that can pay the target value BOOST_AUTO_TEST_CASE(SelectCoins_test) { auto testChain = interfaces::MakeChain(testNode, Params()); CWallet testWallet(testChain.get(), WalletLocation(), WalletDatabase::CreateDummy()); testWallet.SetupLegacyScriptPubKeyMan(); // Random generator stuff std::default_random_engine generator; std::exponential_distribution<double> distribution(100); FastRandomContext rand; // Run this test 100 times for (int i = 0; i < 100; ++i) { empty_wallet(); // Make a wallet with 1000 exponentially distributed random inputs for (int j = 0; j < 1000; ++j) { add_coin(testWallet, int64_t(10000000 * distribution(generator)) * SATOSHI); } // Generate a random fee rate in the range of 100 - 400 CFeeRate rate(int64_t(rand.randrange(300) + 100) * SATOSHI); // Generate a random target value between 1000 and wallet balance Amount target = int64_t(rand.randrange(balance / SATOSHI - 1000) + 1000) * SATOSHI; // Perform selection CoinSelectionParams coin_selection_params_knapsack( false, 34, 148, CFeeRate(Amount::zero()), 0); CoinSelectionParams coin_selection_params_bnb( true, 34, 148, CFeeRate(Amount::zero()), 0); CoinSet out_set; Amount out_value = Amount::zero(); bool bnb_used = false; BOOST_CHECK(testWallet.SelectCoinsMinConf( target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_bnb, bnb_used) || testWallet.SelectCoinsMinConf( target, filter_standard, GroupCoins(vCoins), out_set, out_value, coin_selection_params_knapsack, bnb_used)); BOOST_CHECK_GE(out_value, target); } } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 62c4f92f0..7e099b2ff 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -1,705 +1,699 @@ // Copyright (c) 2012-2019 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 <chain.h> #include <chainparams.h> #include <config.h> #include <interfaces/chain.h> #include <node/context.h> #include <policy/policy.h> #include <rpc/server.h> #include <util/ref.h> #include <util/translation.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/rpcdump.h> #include <wallet/wallet.h> #include <test/util/setup_common.h> #include <wallet/test/wallet_test_fixture.h> #include <boost/test/unit_test.hpp> #include <univalue.h> #include <cstdint> #include <memory> #include <vector> BOOST_FIXTURE_TEST_SUITE(wallet_tests, WalletTestingSetup) static void AddKey(CWallet &wallet, const CKey &key) { auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); spk_man->AddKeyPubKey(key, key.GetPubKey()); } BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); NodeContext node; auto chain = interfaces::MakeChain(node, Params()); // Verify ScanForWalletTransactions fails to read an unknown start block. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); { LOCK(wallet.cs_wallet); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( BlockHash() /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero()); } // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); { LOCK(wallet.cs_wallet); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); } // Prune the older block file. { LOCK(cs_main); Assert(m_node.chainman)->PruneOneBlockFile(oldTip->GetBlockPos().nFile); } UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions only picks transactions in the new block // file. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); { LOCK(wallet.cs_wallet); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); } // Prune the remaining block file. { LOCK(cs_main); Assert(m_node.chainman)->PruneOneBlockFile(newTip->GetBlockPos().nFile); } UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); // Verify ScanForWalletTransactions scans no blocks. { CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); { LOCK(wallet.cs_wallet); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(wallet); reserver.reserve(); CWallet::ScanResult result = wallet.ScanForWalletTransactions( oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, Amount::zero()); } } BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) { // Cap last block file size, and mine new block in a new block file. CBlockIndex *oldTip = ::ChainActive().Tip(); GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); CBlockIndex *newTip = ::ChainActive().Tip(); NodeContext node; auto chain = interfaces::MakeChain(node, Params()); // Prune the older block file. { LOCK(cs_main); Assert(m_node.chainman)->PruneOneBlockFile(oldTip->GetBlockPos().nFile); } UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetupLegacyScriptPubKeyMan(); WITH_LOCK(wallet->cs_wallet, wallet->SetLastBlockProcessed(newTip->nHeight, newTip->GetBlockHash())); AddWallet(wallet); UniValue keys; keys.setArray(); UniValue key; key.setObject(); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(coinbaseKey.GetPubKey()))); key.pushKV("timestamp", 0); key.pushKV("internal", UniValue(true)); keys.push_back(key); key.clear(); key.setObject(); CKey futureKey; futureKey.MakeNewKey(true); key.pushKV("scriptPubKey", HexStr(GetScriptForRawPubKey(futureKey.GetPubKey()))); key.pushKV("timestamp", newTip->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1); key.pushKV("internal", UniValue(true)); keys.push_back(key); util::Ref context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(keys); UniValue response = importmulti(GetConfig(), request); BOOST_CHECK_EQUAL( response.write(), strprintf("[{\"success\":false,\"error\":{\"code\":-1,\"message\":" "\"Rescan failed for key with creation timestamp %d. " "There was an error reading a block from time %d, which " "is after or within %d seconds of key creation, and " "could contain transactions pertaining to the key. As a " "result, transactions and coins using this key may not " "appear in the wallet. This error could be caused by " "pruning or data corruption (see bitcoind log for " "details) and could be dealt with by downloading and " "rescanning the relevant blocks (see -reindex and " "-rescan options).\"}},{\"success\":true}]", 0, oldTip->GetBlockTimeMax(), TIMESTAMP_WINDOW)); RemoveWallet(wallet); } } // Verify importwallet RPC starts rescan at earliest block with timestamp // greater or equal than key birthday. Previously there was a bug where // importwallet RPC would start the scan at the latest block with timestamp less // than or equal to key birthday. BOOST_FIXTURE_TEST_CASE(importwallet_rescan, TestChain100Setup) { // Create two blocks with same timestamp to verify that importwallet rescan // will pick up both blocks, not just the first. const int64_t BLOCK_TIME = ::ChainActive().Tip()->GetBlockTimeMax() + 5; SetMockTime(BLOCK_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); // Set key birthday to block time increased by the timestamp window, so // rescan will start at the block time. const int64_t KEY_TIME = BLOCK_TIME + TIMESTAMP_WINDOW; SetMockTime(KEY_TIME); m_coinbase_txns.emplace_back( CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())) .vtx[0]); NodeContext node; auto chain = interfaces::MakeChain(node, Params()); std::string backup_file = (GetDataDir() / "wallet.backup").string(); // Import key into wallet and call dumpwallet to create backup file. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); { auto spk_man = wallet->GetOrCreateLegacyScriptPubKeyMan(); LOCK2(wallet->cs_wallet, spk_man->cs_KeyStore); spk_man->mapKeyMetadata[coinbaseKey.GetPubKey().GetID()] .nCreateTime = KEY_TIME; spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()); AddWallet(wallet); wallet->SetLastBlockProcessed( ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } util::Ref context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(backup_file); ::dumpwallet(GetConfig(), request); RemoveWallet(wallet); } // Call importwallet RPC and verify all blocks with timestamps >= BLOCK_TIME // were scanned, and no prior blocks were scanned. { std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); LOCK(wallet->cs_wallet); wallet->SetupLegacyScriptPubKeyMan(); util::Ref context; JSONRPCRequest request(context); request.params.setArray(); request.params.push_back(backup_file); AddWallet(wallet); wallet->SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); ::importwallet(GetConfig(), request); RemoveWallet(wallet); BOOST_CHECK_EQUAL(wallet->mapWallet.size(), 3U); BOOST_CHECK_EQUAL(m_coinbase_txns.size(), 103U); for (size_t i = 0; i < m_coinbase_txns.size(); ++i) { bool found = wallet->GetWalletTx(m_coinbase_txns[i]->GetId()); bool expected = i >= 100; BOOST_CHECK_EQUAL(found, expected); } } } // Check that GetImmatureCredit() returns a newly calculated value instead of // the cached value after a MarkDirty() call. // // This is a regression test written to verify a bugfix for the immature credit // function. Similar tests probably should be written for the other credit and // debit functions. BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup) { NodeContext node; auto chain = interfaces::MakeChain(node, Params()); CWallet wallet(chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); CWalletTx wtx(&wallet, m_coinbase_txns.back()); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); wallet.SetLastBlockProcessed(::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 0); wtx.m_confirm = confirm; // Call GetImmatureCredit() once before adding the key to the wallet to // cache the current immature credit amount, which is 0. BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), Amount::zero()); // Invalidate the cached value, add the key, and make sure a new immature // credit amount is calculated. wtx.MarkDirty(); BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50 * COIN); } static int64_t AddTx(CWallet &wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) { CMutableTransaction tx; + CWalletTx::Confirmation confirm; tx.nLockTime = lockTime; SetMockTime(mockTime); CBlockIndex *block = nullptr; if (blockTime > 0) { auto inserted = ::BlockIndex().emplace(BlockHash(GetRandHash()), new CBlockIndex); assert(inserted.second); const BlockHash &hash = inserted.first->first; block = inserted.first->second; block->nTime = blockTime; block->phashBlock = &hash; + confirm = {CWalletTx::Status::CONFIRMED, block->nHeight, hash, 0}; } - CWalletTx wtx(&wallet, MakeTransactionRef(tx)); - LOCK(wallet.cs_wallet); // If transaction is already in map, to avoid inconsistencies, // unconfirmation is needed before confirm again with different block. - std::map<TxId, CWalletTx>::iterator it = wallet.mapWallet.find(wtx.GetId()); - if (it != wallet.mapWallet.end()) { - wtx.setUnconfirmed(); - wallet.AddToWallet(wtx); - } - if (block) { - CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, - block->nHeight, block->GetBlockHash(), - 0); - wtx.m_confirm = confirm; - } - wallet.AddToWallet(wtx); - return wallet.mapWallet.at(wtx.GetId()).nTimeSmart; + return wallet + .AddToWallet(MakeTransactionRef(tx), confirm, + [&](CWalletTx &wtx, bool /* new_tx */) { + wtx.setUnconfirmed(); + return true; + }) + ->nTimeSmart; } // Simple test to verify assignment of CWalletTx::nSmartTime value. Could be // expanded to cover more corner cases of smart time logic. BOOST_AUTO_TEST_CASE(ComputeTimeSmart) { // New transaction should use clock time if lower than block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 100, 120), 100); // Test that updating existing transaction does not change smart time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 1, 200, 220), 100); // New transaction should use clock time if there's no block time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 2, 300, 0), 300); // New transaction should use block time if lower than clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 3, 420, 400), 400); // New transaction should use latest entry time if higher than // min(block time, clock time). BOOST_CHECK_EQUAL(AddTx(m_wallet, 4, 500, 390), 400); // If there are future entries, new transaction should use time of the // newest entry that is no more than 300 seconds ahead of the clock time. BOOST_CHECK_EQUAL(AddTx(m_wallet, 5, 50, 600), 300); // Reset mock time for other tests. SetMockTime(0); } BOOST_AUTO_TEST_CASE(LoadReceiveRequests) { CTxDestination dest = PKHash(); LOCK(m_wallet.cs_wallet); WalletBatch batch{m_wallet.GetDatabase()}; m_wallet.AddDestData(batch, dest, "misc", "val_misc"); m_wallet.AddDestData(batch, dest, "rr0", "val_rr0"); m_wallet.AddDestData(batch, dest, "rr1", "val_rr1"); auto values = m_wallet.GetDestValues("rr"); BOOST_CHECK_EQUAL(values.size(), 2U); BOOST_CHECK_EQUAL(values[0], "val_rr0"); BOOST_CHECK_EQUAL(values[1], "val_rr1"); } // Test some watch-only LegacyScriptPubKeyMan methods by the procedure of // loading (LoadWatchOnly), checking (HaveWatchOnly), getting (GetWatchPubKey) // and removing (RemoveWatchOnly) a given PubKey, resp. its corresponding P2PK // Script. Results of the the impact on the address -> PubKey map is dependent // on whether the PubKey is a point on the curve static void TestWatchOnlyPubKey(LegacyScriptPubKeyMan *spk_man, const CPubKey &add_pubkey) { CScript p2pk = GetScriptForRawPubKey(add_pubkey); CKeyID add_address = add_pubkey.GetID(); CPubKey found_pubkey; LOCK(spk_man->cs_KeyStore); // all Scripts (i.e. also all PubKeys) are added to the general watch-only // set BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk)); spk_man->LoadWatchOnly(p2pk); BOOST_CHECK(spk_man->HaveWatchOnly(p2pk)); // only PubKeys on the curve shall be added to the watch-only address -> // PubKey map bool is_pubkey_fully_valid = add_pubkey.IsFullyValid(); if (is_pubkey_fully_valid) { BOOST_CHECK(spk_man->GetWatchPubKey(add_address, found_pubkey)); BOOST_CHECK(found_pubkey == add_pubkey); } else { BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey)); // passed key is unchanged BOOST_CHECK(found_pubkey == CPubKey()); } spk_man->RemoveWatchOnly(p2pk); BOOST_CHECK(!spk_man->HaveWatchOnly(p2pk)); if (is_pubkey_fully_valid) { BOOST_CHECK(!spk_man->GetWatchPubKey(add_address, found_pubkey)); // passed key is unchanged BOOST_CHECK(found_pubkey == add_pubkey); } } // Cryptographically invalidate a PubKey whilst keeping length and first byte static void PollutePubKey(CPubKey &pubkey) { std::vector<uint8_t> pubkey_raw(pubkey.begin(), pubkey.end()); std::fill(pubkey_raw.begin() + 1, pubkey_raw.end(), 0); pubkey = CPubKey(pubkey_raw); assert(!pubkey.IsFullyValid()); assert(pubkey.IsValid()); } // Test watch-only logic for PubKeys BOOST_AUTO_TEST_CASE(WatchOnlyPubKeys) { CKey key; CPubKey pubkey; LegacyScriptPubKeyMan *spk_man = m_wallet.GetOrCreateLegacyScriptPubKeyMan(); BOOST_CHECK(!spk_man->HaveWatchOnly()); // uncompressed valid PubKey key.MakeNewKey(false); pubkey = key.GetPubKey(); assert(!pubkey.IsCompressed()); TestWatchOnlyPubKey(spk_man, pubkey); // uncompressed cryptographically invalid PubKey PollutePubKey(pubkey); TestWatchOnlyPubKey(spk_man, pubkey); // compressed valid PubKey key.MakeNewKey(true); pubkey = key.GetPubKey(); assert(pubkey.IsCompressed()); TestWatchOnlyPubKey(spk_man, pubkey); // compressed cryptographically invalid PubKey PollutePubKey(pubkey); TestWatchOnlyPubKey(spk_man, pubkey); // invalid empty PubKey pubkey = CPubKey(); TestWatchOnlyPubKey(spk_man, pubkey); } class ListCoinsTestingSetup : public TestChain100Setup { public: ListCoinsTestingSetup() { CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); wallet = std::make_unique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); { LOCK2(wallet->cs_wallet, ::cs_main); wallet->SetLastBlockProcessed( ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash()); } bool firstRun; wallet->LoadWallet(firstRun); AddKey(*wallet, coinbaseKey); WalletRescanReserver reserver(*wallet); reserver.reserve(); CWallet::ScanResult result = wallet->ScanForWalletTransactions( ::ChainActive().Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */); BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(result.last_scanned_block, ::ChainActive().Tip()->GetBlockHash()); BOOST_CHECK_EQUAL(*result.last_scanned_height, ::ChainActive().Height()); BOOST_CHECK(result.last_failed_block.IsNull()); } ~ListCoinsTestingSetup() { wallet.reset(); } CWalletTx &AddTx(CRecipient recipient) { CTransactionRef tx; Amount fee; int changePos = -1; bilingual_str error; CCoinControl dummy; { BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy)); } wallet->CommitTransaction(tx, {}, {}); CMutableTransaction blocktx; { LOCK(wallet->cs_wallet); blocktx = CMutableTransaction(*wallet->mapWallet.at(tx->GetId()).tx); } CreateAndProcessBlock({CMutableTransaction(blocktx)}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); LOCK(wallet->cs_wallet); wallet->SetLastBlockProcessed(wallet->GetLastBlockHeight() + 1, ::ChainActive().Tip()->GetBlockHash()); auto it = wallet->mapWallet.find(tx->GetId()); BOOST_CHECK(it != wallet->mapWallet.end()); CWalletTx::Confirmation confirm( CWalletTx::Status::CONFIRMED, ::ChainActive().Height(), ::ChainActive().Tip()->GetBlockHash(), 1); it->second.m_confirm = confirm; return it->second; } std::unique_ptr<interfaces::Chain> m_chain = interfaces::MakeChain(m_node, Params()); std::unique_ptr<CWallet> wallet; }; BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) { std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); // Confirm ListCoins initially returns 1 coin grouped under coinbaseKey // address. std::map<CTxDestination, std::vector<COutput>> list; { LOCK(wallet->cs_wallet); list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); // Check initial balance from one mature coinbase transaction. BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); // Add a transaction creating a change address, and confirm ListCoins still // returns the coin associated with the change address underneath the // coinbaseKey pubkey, even though the change address has a different // pubkey. AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); { LOCK(wallet->cs_wallet); list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); // Lock both coins. Confirm number of available coins drops to 0. { LOCK(wallet->cs_wallet); std::vector<COutput> available; wallet->AvailableCoins(available); BOOST_CHECK_EQUAL(available.size(), 2U); } for (const auto &group : list) { for (const auto &coin : group.second) { LOCK(wallet->cs_wallet); wallet->LockCoin(COutPoint(coin.tx->GetId(), coin.i)); } } { LOCK(wallet->cs_wallet); std::vector<COutput> available; wallet->AvailableCoins(available); BOOST_CHECK_EQUAL(available.size(), 0U); } // Confirm ListCoins still returns same result as before, despite coins // being locked. { LOCK(wallet->cs_wallet); list = wallet->ListCoins(); } BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(boost::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(list.begin()->second.size(), 2U); } BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup) { NodeContext node; auto chain = interfaces::MakeChain(node, Params()); std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>( chain.get(), WalletLocation(), WalletDatabase::CreateDummy()); wallet->SetupLegacyScriptPubKeyMan(); wallet->SetMinVersion(FEATURE_LATEST); wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS); BOOST_CHECK(!wallet->TopUpKeyPool(1000)); CTxDestination dest; std::string error; BOOST_CHECK( !wallet->GetNewDestination(OutputType::LEGACY, "", dest, error)); } // Explicit calculation which is used to test the wallet constant static size_t CalculateP2PKHInputSize(bool use_max_sig) { // Generate ephemeral valid pubkey CKey key; key.MakeNewKey(true); CPubKey pubkey = key.GetPubKey(); // Generate pubkey hash PKHash key_hash(pubkey); // Create script to enter into keystore. Key hash can't be 0... CScript script = GetScriptForDestination(key_hash); // Add script to key store and key to watchonly FillableSigningProvider keystore; keystore.AddKeyPubKey(key, pubkey); // Fill in dummy signatures for fee calculation. SignatureData sig_data; if (!ProduceSignature(keystore, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, script, sig_data)) { // We're hand-feeding it correct arguments; shouldn't happen assert(false); } CTxIn tx_in; UpdateInput(tx_in, sig_data); return (size_t)GetVirtualTransactionInputSize(tx_in); } BOOST_FIXTURE_TEST_CASE(dummy_input_size_test, TestChain100Setup) { BOOST_CHECK(CalculateP2PKHInputSize(false) <= DUMMY_P2PKH_INPUT_SIZE); BOOST_CHECK_EQUAL(CalculateP2PKHInputSize(true), DUMMY_P2PKH_INPUT_SIZE); } bool malformed_descriptor(std::ios_base::failure e) { std::string s(e.what()); return s.find("Missing checksum") != std::string::npos; } BOOST_FIXTURE_TEST_CASE(wallet_descriptor_test, BasicTestingSetup) { std::vector<uint8_t> malformed_record; CVectorWriter vw(0, 0, malformed_record, 0); vw << std::string("notadescriptor"); vw << (uint64_t)0; vw << (int32_t)0; vw << (int32_t)0; vw << (int32_t)1; VectorReader vr(0, 0, malformed_record, 0); WalletDescriptor w_desc; BOOST_CHECK_EXCEPTION(vr >> w_desc, std::ios_base::failure, malformed_descriptor); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index dd814349b..be52a7dd0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,5048 +1,5046 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 <wallet/wallet.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> #include <interfaces/wallet.h> #include <key.h> #include <key_io.h> #include <policy/mempool.h> #include <policy/policy.h> #include <primitives/transaction.h> #include <random.h> #include <script/descriptor.h> #include <script/script.h> #include <script/sighashtype.h> #include <script/sign.h> #include <script/signingprovider.h> #include <util/bip32.h> #include <util/check.h> #include <util/error.h> #include <util/moneystr.h> #include <util/string.h> #include <util/translation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> #include <boost/algorithm/string/replace.hpp> using interfaces::FoundBlock; const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{ {WALLET_FLAG_AVOID_REUSE, "You need to rescan the blockchain in order to correctly mark used " "destinations in the past. Until this is done, some destinations may " "be considered unused, even if the opposite is the case."}, }; static RecursiveMutex cs_wallets; static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets); static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets); bool AddWallet(const std::shared_ptr<CWallet> &wallet) { LOCK(cs_wallets); assert(wallet); std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i != vpwallets.end()) { return false; } vpwallets.push_back(wallet); wallet->ConnectScriptPubKeyManNotifiers(); return true; } bool RemoveWallet(const std::shared_ptr<CWallet> &wallet) { assert(wallet); // Unregister with the validation interface which also drops shared ponters. wallet->m_chain_notifications_handler.reset(); LOCK(cs_wallets); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i == vpwallets.end()) { return false; } vpwallets.erase(i); return true; } bool HasWallets() { LOCK(cs_wallets); return !vpwallets.empty(); } std::vector<std::shared_ptr<CWallet>> GetWallets() { LOCK(cs_wallets); return vpwallets; } std::shared_ptr<CWallet> GetWallet(const std::string &name) { LOCK(cs_wallets); for (const std::shared_ptr<CWallet> &wallet : vpwallets) { if (wallet->GetName() == name) { return wallet; } } return nullptr; } std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet) { LOCK(cs_wallets); auto it = g_load_wallet_fns.emplace(g_load_wallet_fns.end(), std::move(load_wallet)); return interfaces::MakeHandler([it] { LOCK(cs_wallets); g_load_wallet_fns.erase(it); }); } static Mutex g_wallet_release_mutex; static std::condition_variable g_wallet_release_cv; static std::set<std::string> g_unloading_wallet_set; // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet *wallet) { const std::string name = wallet->GetName(); wallet->WalletLogPrintf("Releasing wallet\n"); wallet->Flush(); delete wallet; // Wallet is now released, notify UnloadWallet, if any. { LOCK(g_wallet_release_mutex); if (g_unloading_wallet_set.erase(name) == 0) { // UnloadWallet was not called for this wallet, all done. return; } } g_wallet_release_cv.notify_all(); } void UnloadWallet(std::shared_ptr<CWallet> &&wallet) { // Mark wallet for unloading. const std::string name = wallet->GetName(); { LOCK(g_wallet_release_mutex); auto it = g_unloading_wallet_set.insert(name); assert(it.second); } // The wallet can be in use so it's not possible to explicitly unload here. // Notify the unload intent so that all remaining shared pointers are // released. wallet->NotifyUnload(); // Time to ditch our shared_ptr and wait for ReleaseWallet call. wallet.reset(); { WAIT_LOCK(g_wallet_release_mutex, lock); while (g_unloading_wallet_set.count(name) == 1) { g_wallet_release_cv.wait(lock); } } } static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10; std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error, std::vector<bilingual_str> &warnings) { try { if (!CWallet::Verify(chainParams, chain, location, error, warnings)) { error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; return nullptr; } std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile( chainParams, chain, location, error, warnings); if (!wallet) { error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error; return nullptr; } AddWallet(wallet); wallet->postInitProcess(); return wallet; } catch (const std::runtime_error &e) { error = Untranslated(e.what()); return nullptr; } } std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const std::string &name, bilingual_str &error, std::vector<bilingual_str> &warnings) { return LoadWallet(chainParams, chain, WalletLocation(name), error, warnings); } WalletCreationStatus CreateWallet(const CChainParams ¶ms, interfaces::Chain &chain, const SecureString &passphrase, uint64_t wallet_creation_flags, const std::string &name, bilingual_str &error, std::vector<bilingual_str> &warnings, std::shared_ptr<CWallet> &result) { // Indicate that the wallet is actually supposed to be blank and not just // blank to make it encrypted bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); // Born encrypted wallets need to be created blank first. if (!passphrase.empty()) { wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET; } // Check the wallet file location WalletLocation location(name); if (location.Exists()) { error = strprintf(Untranslated("Wallet %s already exists."), location.GetName()); return WalletCreationStatus::CREATION_FAILED; } // Wallet::Verify will check if we're trying to create a wallet with a // duplicate name. if (!CWallet::Verify(params, chain, location, error, warnings)) { error = Untranslated("Wallet file verification failed.") + Untranslated(" ") + error; return WalletCreationStatus::CREATION_FAILED; } // Do not allow a passphrase when private keys are disabled if (!passphrase.empty() && (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { error = Untranslated( "Passphrase provided but private keys are disabled. A passphrase " "is only used to encrypt private keys, so cannot be used for " "wallets with private keys disabled."); return WalletCreationStatus::CREATION_FAILED; } // Make the wallet std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile( params, chain, location, error, warnings, wallet_creation_flags); if (!wallet) { error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error; return WalletCreationStatus::CREATION_FAILED; } // Encrypt the wallet if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!wallet->EncryptWallet(passphrase)) { error = Untranslated("Error: Wallet created but failed to encrypt."); return WalletCreationStatus::ENCRYPTION_FAILED; } if (!create_blank) { // Unlock the wallet if (!wallet->Unlock(passphrase)) { error = Untranslated( "Error: Wallet was encrypted but could not be unlocked"); return WalletCreationStatus::ENCRYPTION_FAILED; } // Set a seed for the wallet { LOCK(wallet->cs_wallet); if (wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { wallet->SetupDescriptorScriptPubKeyMans(); } else { for (auto spk_man : wallet->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { error = Untranslated("Unable to generate initial keys"); return WalletCreationStatus::CREATION_FAILED; } } } } // Relock the wallet wallet->Lock(); } } AddWallet(wallet); wallet->postInitProcess(); result = wallet; return WalletCreationStatus::SUCCESS; } const BlockHash CWalletTx::ABANDON_HASH(UINT256_ONE()); /** @defgroup mapWallet * * @{ */ std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } const CChainParams &CWallet::GetChainParams() const { // Get CChainParams from interfaces::Chain, unless wallet doesn't have a // chain (i.e. bitcoin-wallet), in which case return global Params() return m_chain ? m_chain->params() : Params(); } const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return nullptr; } return &(it->second); } void CWallet::UpgradeKeyMetadata() { if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { return; } auto spk_man = GetLegacyScriptPubKeyMan(); if (!spk_man) { return; } spk_man->UpgradeKeyMetadata(); SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); } bool CWallet::Unlock(const SecureString &strWalletPassphrase, bool accept_no_keys) { CCrypter crypter; CKeyingMaterial _vMasterKey; { LOCK(cs_wallet); for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { // try another master key continue; } if (Unlock(_vMasterKey, accept_no_keys)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); return true; } } } return false; } bool CWallet::ChangeWalletPassphrase( const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase) { bool fWasLocked = IsLocked(); LOCK(cs_wallet); Lock(); CCrypter crypter; CKeyingMaterial _vMasterKey; for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { return false; } if (Unlock(_vMasterKey)) { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = static_cast<unsigned int>( pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)))); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>( pMasterKey.second.nDeriveIterations * 100 / double(GetTimeMillis() - nStartTime))) / 2; if (pMasterKey.second.nDeriveIterations < 25000) { pMasterKey.second.nDeriveIterations = 25000; } WalletLogPrintf( "Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase( strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey)) { return false; } WalletBatch(*database).WriteMasterKey(pMasterKey.first, pMasterKey.second); if (fWasLocked) { Lock(); } return true; } } return false; } void CWallet::chainStateFlushed(const CBlockLocator &loc) { WalletBatch batch(*database); batch.WriteBestBlock(loc); } void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch *batch_in, bool fExplicit) { LOCK(cs_wallet); if (nWalletVersion >= nVersion) { return; } // When doing an explicit upgrade, if we pass the max version permitted, // upgrade all the way. if (fExplicit && nVersion > nWalletMaxVersion) { nVersion = FEATURE_LATEST; } nWalletVersion = nVersion; if (nVersion > nWalletMaxVersion) { nWalletMaxVersion = nVersion; } WalletBatch *batch = batch_in ? batch_in : new WalletBatch(*database); if (nWalletVersion > 40000) { batch->WriteMinVersion(nWalletVersion); } if (!batch_in) { delete batch; } } bool CWallet::SetMaxVersion(int nVersion) { LOCK(cs_wallet); // Cannot downgrade below current version if (nWalletVersion > nVersion) { return false; } nWalletMaxVersion = nVersion; return true; } std::set<TxId> CWallet::GetConflicts(const TxId &txid) const { std::set<TxId> result; AssertLockHeld(cs_wallet); std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return result; } const CWalletTx &wtx = it->second; std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; for (const CTxIn &txin : wtx.tx->vin) { if (mapTxSpends.count(txin.prevout) <= 1) { // No conflict if zero or one spends. continue; } range = mapTxSpends.equal_range(txin.prevout); for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) { result.insert(_it->second); } } return result; } bool CWallet::HasWalletSpend(const TxId &txid) const { AssertLockHeld(cs_wallet); auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid); } void CWallet::Flush(bool shutdown) { database->Flush(shutdown); } void CWallet::SyncMetaData( std::pair<TxSpends::iterator, TxSpends::iterator> range) { // We want all the wallet transactions in range to have the same metadata as // the oldest (smallest nOrderPos). // So: find smallest nOrderPos: int nMinOrderPos = std::numeric_limits<int>::max(); const CWalletTx *copyFrom = nullptr; for (TxSpends::iterator it = range.first; it != range.second; ++it) { const CWalletTx *wtx = &mapWallet.at(it->second); if (wtx->nOrderPos < nMinOrderPos) { nMinOrderPos = wtx->nOrderPos; copyFrom = wtx; } } if (!copyFrom) { return; } // Now copy data from copyFrom to rest: for (TxSpends::iterator it = range.first; it != range.second; ++it) { const TxId &txid = it->second; CWalletTx *copyTo = &mapWallet.at(txid); if (copyFrom == copyTo) { continue; } assert( copyFrom && "Oldest wallet transaction in range assumed to have been found."); if (!copyFrom->IsEquivalentTo(*copyTo)) { continue; } copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; // fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied // on purpose. copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; // nOrderPos not copied on purpose cached members not copied on purpose. } } /** * Outpoint is spent if any non-conflicted transaction, spends it: */ bool CWallet::IsSpent(const COutPoint &outpoint) const { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(outpoint); for (TxSpends::const_iterator it = range.first; it != range.second; ++it) { const TxId &wtxid = it->second; std::map<TxId, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { int depth = mit->second.GetDepthInMainChain(); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) { // Spent return true; } } } return false; } void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) { mapTxSpends.insert(std::make_pair(outpoint, wtxid)); setLockedCoins.erase(outpoint); std::pair<TxSpends::iterator, TxSpends::iterator> range; range = mapTxSpends.equal_range(outpoint); SyncMetaData(range); } void CWallet::AddToSpends(const TxId &wtxid) { auto it = mapWallet.find(wtxid); assert(it != mapWallet.end()); CWalletTx &thisTx = it->second; // Coinbases don't spend anything! if (thisTx.IsCoinBase()) { return; } for (const CTxIn &txin : thisTx.tx->vin) { AddToSpends(txin.prevout, wtxid); } } bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) { if (IsCrypted()) { return false; } CKeyingMaterial _vMasterKey; _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); CMasterKey kMasterKey; kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); CCrypter crypter; int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = static_cast<unsigned int>( 2500000 / double(GetTimeMillis() - nStartTime)); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 / double(GetTimeMillis() - nStartTime))) / 2; if (kMasterKey.nDeriveIterations < 25000) { kMasterKey.nDeriveIterations = 25000; } WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) { return false; } { LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; WalletBatch *encrypted_batch = new WalletBatch(*database); if (!encrypted_batch->TxnBegin()) { delete encrypted_batch; encrypted_batch = nullptr; return false; } encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); for (const auto &spk_man_pair : m_spk_managers) { auto spk_man = spk_man_pair.second.get(); if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) { encrypted_batch->TxnAbort(); delete encrypted_batch; encrypted_batch = nullptr; // We now probably have half of our keys encrypted in memory, // and half not... die and let the user reload the unencrypted // wallet. assert(false); } } // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true); if (!encrypted_batch->TxnCommit()) { delete encrypted_batch; encrypted_batch = nullptr; // We now have keys encrypted in memory, but not on disk... // die to avoid confusion and let the user reload the unencrypted // wallet. assert(false); } delete encrypted_batch; encrypted_batch = nullptr; Lock(); Unlock(strWalletPassphrase); // If we are using descriptors, make new descriptors with a new seed if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)) { SetupDescriptorScriptPubKeyMans(); } else if (auto spk_man = GetLegacyScriptPubKeyMan()) { // if we are using HD, replace the HD seed with a new one if (spk_man->IsHDEnabled()) { if (!spk_man->SetupGeneration(true)) { return false; } } } Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might // keep bits of the unencrypted private key in slack space in the // database file. database->Rewrite(); // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: database->ReloadDbEnv(); } NotifyStatusChanged(this); return true; } DBErrors CWallet::ReorderTransactions() { LOCK(cs_wallet); WalletBatch batch(*database); // Old wallets didn't have any defined order for transactions. Probably a // bad idea to change the output of this. // First: get all CWalletTx into a sorted-by-time // multimap. TxItems txByTime; for (auto &entry : mapWallet) { CWalletTx *wtx = &entry.second; txByTime.insert(std::make_pair(wtx->nTimeReceived, wtx)); } nOrderPosNext = 0; std::vector<int64_t> nOrderPosOffsets; for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) { CWalletTx *const pwtx = (*it).second; int64_t &nOrderPos = pwtx->nOrderPos; if (nOrderPos == -1) { nOrderPos = nOrderPosNext++; nOrderPosOffsets.push_back(nOrderPos); if (!batch.WriteTx(*pwtx)) { return DBErrors::LOAD_FAIL; } } else { int64_t nOrderPosOff = 0; for (const int64_t &nOffsetStart : nOrderPosOffsets) { if (nOrderPos >= nOffsetStart) { ++nOrderPosOff; } } nOrderPos += nOrderPosOff; nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); if (!nOrderPosOff) { continue; } // Since we're changing the order, write it back. if (!batch.WriteTx(*pwtx)) { return DBErrors::LOAD_FAIL; } } } batch.WriteOrderPosNext(nOrderPosNext); return DBErrors::LOAD_OK; } int64_t CWallet::IncOrderPosNext(WalletBatch *batch) { AssertLockHeld(cs_wallet); int64_t nRet = nOrderPosNext++; if (batch) { batch->WriteOrderPosNext(nOrderPosNext); } else { WalletBatch(*database).WriteOrderPosNext(nOrderPosNext); } return nRet; } void CWallet::MarkDirty() { LOCK(cs_wallet); for (std::pair<const TxId, CWalletTx> &item : mapWallet) { item.second.MarkDirty(); } } void CWallet::SetSpentKeyState(WalletBatch &batch, const TxId &txid, unsigned int n, bool used, std::set<CTxDestination> &tx_destinations) { AssertLockHeld(cs_wallet); const CWalletTx *srctx = GetWalletTx(txid); if (!srctx) { return; } CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { if (IsMine(dst)) { if (used && !GetDestData(dst, "used", nullptr)) { // p for "present", opposite of absent (null) if (AddDestData(batch, dst, "used", "p")) { tx_destinations.insert(dst); } } else if (!used && GetDestData(dst, "used", nullptr)) { EraseDestData(batch, dst, "used"); } } } } bool CWallet::IsSpentKey(const TxId &txid, unsigned int n) const { AssertLockHeld(cs_wallet); CTxDestination dst; const CWalletTx *srctx = GetWalletTx(txid); if (srctx) { assert(srctx->tx->vout.size() > n); CTxDestination dest; if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) { return false; } if (GetDestData(dest, "used", nullptr)) { return true; } if (IsLegacy()) { LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan(); assert(spk_man != nullptr); for (const auto &keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) { PKHash pkh_dest(keyid); if (GetDestData(pkh_dest, "used", nullptr)) { return true; } } } } return false; } -bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) { +CWalletTx *CWallet::AddToWallet(CTransactionRef tx, + const CWalletTx::Confirmation &confirm, + const UpdateWalletTxFn &update_wtx, + bool fFlushOnClose) { LOCK(cs_wallet); WalletBatch batch(*database, "r+", fFlushOnClose); - const TxId &txid = wtxIn.GetId(); + const TxId &txid = tx->GetId(); if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // Mark used destinations std::set<CTxDestination> tx_destinations; - for (const CTxIn &txin : wtxIn.tx->vin) { + for (const CTxIn &txin : tx->vin) { const COutPoint &op = txin.prevout; SetSpentKeyState(batch, op.GetTxId(), op.GetN(), true, tx_destinations); } MarkDestinationsDirty(tx_destinations); } // Inserts only if not already there, returns tx inserted or tx found. - std::pair<std::map<TxId, CWalletTx>::iterator, bool> ret = - mapWallet.insert(std::make_pair(txid, wtxIn)); + auto ret = + mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), + std::forward_as_tuple(this, tx)); CWalletTx &wtx = (*ret.first).second; wtx.BindWallet(this); bool fInsertedNew = ret.second; + bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew); if (fInsertedNew) { + wtx.m_confirm = confirm; wtx.nTimeReceived = chain().getAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx); AddToSpends(txid); } - bool fUpdated = false; if (!fInsertedNew) { - if (wtxIn.m_confirm.status != wtx.m_confirm.status) { - wtx.m_confirm.status = wtxIn.m_confirm.status; - wtx.m_confirm.nIndex = wtxIn.m_confirm.nIndex; - wtx.m_confirm.hashBlock = wtxIn.m_confirm.hashBlock; - wtx.m_confirm.block_height = wtxIn.m_confirm.block_height; + if (confirm.status != wtx.m_confirm.status) { + wtx.m_confirm.status = confirm.status; + wtx.m_confirm.nIndex = confirm.nIndex; + wtx.m_confirm.hashBlock = confirm.hashBlock; + wtx.m_confirm.block_height = confirm.block_height; fUpdated = true; } else { - assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex); - assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock); - assert(wtx.m_confirm.block_height == wtxIn.m_confirm.block_height); - } - - if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { - wtx.fFromMe = wtxIn.fFromMe; - fUpdated = true; + assert(wtx.m_confirm.nIndex == confirm.nIndex); + assert(wtx.m_confirm.hashBlock == confirm.hashBlock); + assert(wtx.m_confirm.block_height == confirm.block_height); } } //// debug print - WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(), + WalletLogPrintf("AddToWallet %s %s%s\n", txid.ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if ((fInsertedNew || fUpdated) && !batch.WriteTx(wtx)) { - return false; + return nullptr; } // Break debit/credit balance caches: wtx.MarkDirty(); // Notify UI of new or updated transaction. NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED); #if defined(HAVE_SYSTEM) // Notify an external script when a wallet transaction comes in or is // updated. std::string strCmd = gArgs.GetArg("-walletnotify", ""); if (!strCmd.empty()) { - boost::replace_all(strCmd, "%s", wtxIn.GetId().GetHex()); + boost::replace_all(strCmd, "%s", txid.GetHex()); #ifndef WIN32 // Substituting the wallet name isn't currently supported on windows // because windows shell escaping has not been implemented yet: // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-537384875 // A few ways it could be implemented in the future are described in: // https://github.com/bitcoin/bitcoin/pull/13339#issuecomment-461288094 boost::replace_all(strCmd, "%w", ShellEscape(GetName())); #endif std::thread t(runCommand, strCmd); // Thread runs free. t.detach(); } #endif - return true; + return &wtx; } void CWallet::LoadToWallet(CWalletTx &wtxIn) { // If wallet doesn't have a chain (e.g wallet-tool), don't bother to update // txn. if (HaveChain()) { std::optional<int> block_height = chain().getBlockHeight(wtxIn.m_confirm.hashBlock); if (block_height) { // Update cached block height variable since it not stored in the // serialized transaction. wtxIn.m_confirm.block_height = *block_height; } else if (wtxIn.isConflicted() || wtxIn.isConfirmed()) { // If tx block (or conflicting block) was reorged out of chain // while the wallet was shutdown, change tx status to UNCONFIRMED // and reset block height, hash, and index. ABANDONED tx don't have // associated blocks and don't need to be updated. The case where a // transaction was reorged out while online and then reconfirmed // while offline is covered by the rescan logic. wtxIn.setUnconfirmed(); wtxIn.m_confirm.hashBlock = BlockHash(); wtxIn.m_confirm.block_height = 0; wtxIn.m_confirm.nIndex = 0; } } const TxId &txid = wtxIn.GetId(); const auto &ins = mapWallet.emplace(txid, wtxIn); CWalletTx &wtx = ins.first->second; wtx.BindWallet(this); if (/* insertion took place */ ins.second) { wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); } AddToSpends(txid); for (const CTxIn &txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.GetTxId()); if (it != mapWallet.end()) { CWalletTx &prevtx = it->second; if (prevtx.isConflicted()) { MarkConflicted(prevtx.m_confirm.hashBlock, prevtx.m_confirm.block_height, wtx.GetId()); } } } } bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, CWalletTx::Confirmation confirm, bool fUpdate) { const CTransaction &tx = *ptx; AssertLockHeld(cs_wallet); if (!confirm.hashBlock.IsNull()) { for (const CTxIn &txin : tx.vin) { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetId()) { WalletLogPrintf( "Transaction %s (in block %s) conflicts with wallet " "transaction %s (both spend %s:%i)\n", tx.GetId().ToString(), confirm.hashBlock.ToString(), range.first->second.ToString(), range.first->first.GetTxId().ToString(), range.first->first.GetN()); MarkConflicted(confirm.hashBlock, confirm.block_height, range.first->second); } range.first++; } } } bool fExisted = mapWallet.count(tx.GetId()) != 0; if (fExisted && !fUpdate) { return false; } if (fExisted || IsMine(tx) || IsFromMe(tx)) { /** * Check if any keys in the wallet keypool that were supposed to be * unused have appeared in a new transaction. If so, remove those keys * from the keypool. This can happen when restoring an old wallet backup * that does not contain the mostly recently created transactions from * newer versions of the wallet. */ // loop though all outputs for (const CTxOut &txout : tx.vout) { for (const auto &spk_man_pair : m_spk_managers) { spk_man_pair.second->MarkUnusedAddresses(txout.scriptPubKey); } } - CWalletTx wtx(this, ptx); - // Block disconnection override an abandoned tx as unconfirmed // which means user may have to call abandontransaction again - wtx.m_confirm = confirm; - - return AddToWallet(wtx, false); + return AddToWallet(MakeTransactionRef(tx), confirm, + /* update_wtx= */ nullptr, + /* fFlushOnClose= */ false); } - return false; } bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const { LOCK(cs_wallet); const CWalletTx *wtx = GetWalletTx(txid); return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef &tx) { for (const CTxIn &txin : tx->vin) { auto it = mapWallet.find(txin.prevout.GetTxId()); if (it != mapWallet.end()) { it->second.MarkDirty(); } } } bool CWallet::AbandonTransaction(const TxId &txid) { LOCK(cs_wallet); WalletBatch batch(*database, "r+"); std::set<TxId> todo; std::set<TxId> done; // Can't mark abandoned if confirmed or in mempool auto it = mapWallet.find(txid); assert(it != mapWallet.end()); CWalletTx &origtx = it->second; if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { return false; } todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx &wtx = it->second; int currentconfirm = wtx.GetDepthInMainChain(); // If the orig tx was not in block, none of its spends can be. assert(currentconfirm <= 0); // If (currentconfirm < 0) {Tx and spends are already conflicted, no // need to abandon} if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can // be in mempool. assert(!wtx.InMempool()); wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet // that spend them abandoned too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. MarkInputsDirty(wtx.tx); } } return true; } void CWallet::MarkConflicted(const BlockHash &hashBlock, int conflicting_height, const TxId &txid) { LOCK(cs_wallet); int conflictconfirms = (m_last_block_processed_height - conflicting_height + 1) * -1; // If number of conflict confirms cannot be determined, this means that the // block is still unknown or not yet part of the main chain, for example // when loading the wallet during a reindex. Do nothing in that case. if (conflictconfirms >= 0) { return; } // Do not flush the wallet here for performance reasons. WalletBatch batch(*database, "r+", false); std::set<TxId> todo; std::set<TxId> done; todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx &wtx = it->second; int currentconfirm = wtx.GetDepthInMainChain(); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. wtx.m_confirm.nIndex = 0; wtx.m_confirm.hashBlock = hashBlock; wtx.m_confirm.block_height = conflicting_height; wtx.setConflicted(); wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet // that spend them conflicted too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. MarkInputsDirty(wtx.tx); } } } void CWallet::SyncTransaction(const CTransactionRef &ptx, CWalletTx::Confirmation confirm, bool update_tx) { if (!AddToWalletIfInvolvingMe(ptx, confirm, update_tx)) { // Not one of ours return; } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be // recomputed, also: MarkInputsDirty(ptx); } void CWallet::transactionAddedToMempool(const CTransactionRef &ptx) { LOCK(cs_wallet); CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, BlockHash(), /* nIndex */ 0); SyncTransaction(ptx, confirm); auto it = mapWallet.find(ptx->GetId()); if (it != mapWallet.end()) { it->second.fInMempool = true; } } void CWallet::transactionRemovedFromMempool(const CTransactionRef &ptx) { LOCK(cs_wallet); auto it = mapWallet.find(ptx->GetId()); if (it != mapWallet.end()) { it->second.fInMempool = false; } } void CWallet::blockConnected(const CBlock &block, int height) { const BlockHash &block_hash = block.GetHash(); LOCK(cs_wallet); m_last_block_processed_height = height; m_last_block_processed = block_hash; for (size_t index = 0; index < block.vtx.size(); index++) { CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, height, block_hash, index); SyncTransaction(block.vtx[index], confirm); transactionRemovedFromMempool(block.vtx[index]); } } void CWallet::blockDisconnected(const CBlock &block, int height) { LOCK(cs_wallet); // At block disconnection, this will change an abandoned transaction to // be unconfirmed, whether or not the transaction is added back to the // mempool. User may have to call abandontransaction again. It may be // addressed in the future with a stickier abandoned state or even removing // abandontransaction call. m_last_block_processed_height = height - 1; m_last_block_processed = block.hashPrevBlock; for (const CTransactionRef &ptx : block.vtx) { CWalletTx::Confirmation confirm(CWalletTx::Status::UNCONFIRMED, /* block_height */ 0, BlockHash(), /* nIndex */ 0); SyncTransaction(ptx, confirm); } } void CWallet::updatedBlockTip() { m_best_block_time = GetTime(); } void CWallet::BlockUntilSyncedToCurrentChain() const { AssertLockNotHeld(cs_wallet); // Skip the queue-draining stuff if we know we're caught up with // chainActive.Tip(), otherwise put a callback in the validation interface // queue and wait for the queue to drain enough to execute it (indicating we // are caught up at least with the time we entered this function). const BlockHash last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); chain().waitForNotificationsIfTipChanged(last_block_hash); } isminetype CWallet::IsMine(const CTxIn &txin) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { return IsMine(prev.tx->vout[txin.prevout.GetN()]); } } return ISMINE_NO; } // Note that this function doesn't distinguish between a 0-valued input, and a // not-"is mine" (according to the filter) input. Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) { return prev.tx->vout[txin.prevout.GetN()].nValue; } } } return Amount::zero(); } isminetype CWallet::IsMine(const CTxOut &txout) const { return IsMine(txout.scriptPubKey); } isminetype CWallet::IsMine(const CTxDestination &dest) const { return IsMine(GetScriptForDestination(dest)); } isminetype CWallet::IsMine(const CScript &script) const { isminetype result = ISMINE_NO; for (const auto &spk_man_pair : m_spk_managers) { result = std::max(result, spk_man_pair.second->IsMine(script)); } return result; } Amount CWallet::GetCredit(const CTxOut &txout, const isminefilter &filter) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsMine(txout) & filter) ? txout.nValue : Amount::zero(); } bool CWallet::IsChange(const CTxOut &txout) const { return IsChange(txout.scriptPubKey); } bool CWallet::IsChange(const CScript &script) const { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book is // change. That assumption is likely to break when we implement // multisignature wallets that return change back into a // multi-signature-protected address; a better way of identifying which // outputs are 'the send' and which are 'the change' will need to be // implemented (maybe extend CWalletTx to remember which output, if any, was // change). if (IsMine(script)) { CTxDestination address; if (!ExtractDestination(script, address)) { return true; } LOCK(cs_wallet); if (!FindAddressBookEntry(address)) { return true; } } return false; } Amount CWallet::GetChange(const CTxOut &txout) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsChange(txout) ? txout.nValue : Amount::zero()); } bool CWallet::IsMine(const CTransaction &tx) const { for (const CTxOut &txout : tx.vout) { if (IsMine(txout)) { return true; } } return false; } bool CWallet::IsFromMe(const CTransaction &tx) const { return GetDebit(tx, ISMINE_ALL) > Amount::zero(); } Amount CWallet::GetDebit(const CTransaction &tx, const isminefilter &filter) const { Amount nDebit = Amount::zero(); for (const CTxIn &txin : tx.vin) { nDebit += GetDebit(txin, filter); if (!MoneyRange(nDebit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nDebit; } bool CWallet::IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const { LOCK(cs_wallet); for (const CTxIn &txin : tx.vin) { auto mi = mapWallet.find(txin.prevout.GetTxId()); if (mi == mapWallet.end()) { // Any unknown inputs can't be from us. return false; } const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() >= prev.tx->vout.size()) { // Invalid input! return false; } if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) { return false; } } return true; } Amount CWallet::GetCredit(const CTransaction &tx, const isminefilter &filter) const { Amount nCredit = Amount::zero(); for (const CTxOut &txout : tx.vout) { nCredit += GetCredit(txout, filter); if (!MoneyRange(nCredit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nCredit; } Amount CWallet::GetChange(const CTransaction &tx) const { Amount nChange = Amount::zero(); for (const CTxOut &txout : tx.vout) { nChange += GetChange(txout); if (!MoneyRange(nChange)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nChange; } bool CWallet::IsHDEnabled() const { // All Active ScriptPubKeyMans must be HD for this to be true bool result = true; for (const auto &spk_man : GetActiveScriptPubKeyMans()) { result &= spk_man->IsHDEnabled(); } return result; } bool CWallet::CanGetAddresses(bool internal) const { LOCK(cs_wallet); if (m_spk_managers.empty()) { return false; } for (OutputType t : OUTPUT_TYPES) { auto spk_man = GetScriptPubKeyMan(t, internal); if (spk_man && spk_man->CanGetAddresses(internal)) { return true; } } return false; } void CWallet::SetWalletFlag(uint64_t flags) { LOCK(cs_wallet); m_wallet_flags |= flags; if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } } void CWallet::UnsetWalletFlag(uint64_t flag) { WalletBatch batch(*database); UnsetWalletFlagWithDB(batch, flag); } void CWallet::UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag) { LOCK(cs_wallet); m_wallet_flags &= ~flag; if (!batch.WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } } void CWallet::UnsetBlankWalletFlag(WalletBatch &batch) { UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); } bool CWallet::IsWalletFlagSet(uint64_t flag) const { return (m_wallet_flags & flag); } bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly) { LOCK(cs_wallet); m_wallet_flags = overwriteFlags; if (((overwriteFlags & KNOWN_WALLET_FLAGS) >> 32) ^ (overwriteFlags >> 32)) { // contains unknown non-tolerable wallet flags return false; } if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } return true; } int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; return n ? n : nTimeReceived; } // Helper for producing a max-sized low-S low-R signature (eg 71 bytes) // or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const { // Fill in dummy signatures for fee calculation. const CScript &scriptPubKey = txout.scriptPubKey; SignatureData sigdata; std::unique_ptr<SigningProvider> provider = GetSolvingProvider(scriptPubKey); if (!provider) { // We don't know about this scriptpbuKey; return false; } if (!ProduceSignature(*provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } UpdateInput(tx_in, sigdata); return true; } // Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 // bytes) bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const { // Fill in dummy signatures for fee calculation. int nIn = 0; for (const auto &txout : txouts) { if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) { return false; } nIn++; } return true; } bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) { auto spk_man = GetLegacyScriptPubKeyMan(); if (!spk_man) { return false; } LOCK(spk_man->cs_KeyStore); return spk_man->ImportScripts(scripts, timestamp); } bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map, const int64_t timestamp) { auto spk_man = GetLegacyScriptPubKeyMan(); if (!spk_man) { return false; } LOCK(spk_man->cs_KeyStore); return spk_man->ImportPrivKeys(privkey_map, timestamp); } bool CWallet::ImportPubKeys( const std::vector<CKeyID> &ordered_pubkeys, const std::map<CKeyID, CPubKey> &pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) { auto spk_man = GetLegacyScriptPubKeyMan(); if (!spk_man) { return false; } LOCK(spk_man->cs_KeyStore); return spk_man->ImportPubKeys(ordered_pubkeys, pubkey_map, key_origins, add_keypool, internal, timestamp); } bool CWallet::ImportScriptPubKeys(const std::string &label, const std::set<CScript> &script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) { auto spk_man = GetLegacyScriptPubKeyMan(); if (!spk_man) { return false; } LOCK(spk_man->cs_KeyStore); if (!spk_man->ImportScriptPubKeys(script_pub_keys, have_solving_data, timestamp)) { return false; } if (apply_label) { WalletBatch batch(*database); for (const CScript &script : script_pub_keys) { CTxDestination dest; ExtractDestination(script, dest); if (IsValidDestination(dest)) { SetAddressBookWithDB(batch, dest, label, "receive"); } } } return true; } int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; for (auto &input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.GetTxId()); // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { return -1; } assert(input.prevout.GetN() < mi->second.tx->vout.size()); txouts.emplace_back(mi->second.tx->vout[input.prevout.GetN()]); } return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); } // txouts needs to be in the order of tx.vin int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut> &txouts, bool use_max_sig) { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { return -1; } return GetSerializeSize(txNew, PROTOCOL_VERSION); } int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *wallet, bool use_max_sig) { CMutableTransaction txn; txn.vin.push_back(CTxIn(COutPoint())); if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { return -1; } return GetSerializeSize(txn.vin[0], PROTOCOL_VERSION); } void CWalletTx::GetAmounts(std::list<COutputEntry> &listReceived, std::list<COutputEntry> &listSent, Amount &nFee, const isminefilter &filter) const { nFee = Amount::zero(); listReceived.clear(); listSent.clear(); // Compute fee: Amount nDebit = GetDebit(filter); // debit>0 means we signed/sent this transaction. if (nDebit > Amount::zero()) { Amount nValueOut = tx->GetValueOut(); nFee = (nDebit - nValueOut); } // Sent/received. for (unsigned int i = 0; i < tx->vout.size(); ++i) { const CTxOut &txout = tx->vout[i]; isminetype fIsMine = pwallet->IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > Amount::zero()) { // Don't report 'change' txouts if (pwallet->IsChange(txout)) { continue; } } else if (!(fIsMine & filter)) { continue; } // In either case, we need to get the destination address. CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown " "transaction type found, txid %s\n", this->GetId().ToString()); address = CNoDestination(); } COutputEntry output = {address, txout.nValue, (int)i}; // If we are debited by the transaction, add the output as a "sent" // entry. if (nDebit > Amount::zero()) { listSent.push_back(output); } // If we are receiving the output, add it as a "received" entry. if (fIsMine & filter) { listReceived.push_back(output); } } } /** * Scan active chain for relevant transactions after importing keys. This should * be called whenever new keys are added to the wallet, with the oldest key * creation time. * * @return Earliest timestamp that could be successfully scanned from. Timestamp * returned will be higher than startTime if relevant blocks could not be read. */ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver &reserver, bool update) { // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. int start_height = 0; BlockHash start_block; bool start = chain().findFirstBlockWithTimeAndHeight( startTime - TIMESTAMP_WINDOW, 0, FoundBlock().hash(start_block).height(start_height)); WalletLogPrintf("%s: Rescanning last %i blocks\n", __func__, start ? WITH_LOCK(cs_wallet, return GetLastBlockHeight()) - start_height + 1 : 0); if (start) { // TODO: this should take into account failure by ScanResult::USER_ABORT ScanResult result = ScanForWalletTransactions( start_block, start_height, {} /* max_height */, reserver, update); if (result.status == ScanResult::FAILURE) { int64_t time_max; CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max))); return time_max + TIMESTAMP_WINDOW + 1; } } return startTime; } /** * Scan the block chain (starting in start_block) for transactions from or to * us. If fUpdate is true, found transactions that already exist in the wallet * will be updated. * * @param[in] start_block Scan starting block. If block is not on the active * chain, the scan will return SUCCESS immediately. * @param[in] start_height Height of start_block * @param[in] max_height Optional max scanning height. If unset there is * no maximum and scanning can continue to the tip * * @return ScanResult returning scan information and indicating success or * failure. Return status will be set to SUCCESS if scan was * successful. FAILURE if a complete rescan was not possible (due to * pruning or corruption). USER_ABORT if the rescan was aborted before * it could complete. * * @pre Caller needs to make sure start_block (and the optional stop_block) are * on the main chain after to the addition of any new keys you want to detect * transactions for. */ CWallet::ScanResult CWallet::ScanForWalletTransactions( const BlockHash &start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver &reserver, bool fUpdate) { int64_t nNow = GetTime(); int64_t start_time = GetTimeMillis(); assert(reserver.isReserved()); BlockHash block_hash = start_block; ScanResult result; WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); fAbortRescan = false; // Show rescan progress in GUI as dialog or on splashscreen, if -rescan on // startup. ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); BlockHash tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash()); BlockHash end_hash = tip_hash; if (max_height) { chain().findAncestorByHeight(tip_hash, *max_height, FoundBlock().hash(end_hash)); } double progress_begin = chain().guessVerificationProgress(block_hash); double progress_end = chain().guessVerificationProgress(end_hash); double progress_current = progress_begin; int block_height = start_height; while (!fAbortRescan && !chain().shutdownRequested()) { m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); if (block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, (int)(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current); } CBlock block; bool next_block; BlockHash next_block_hash; bool reorg = false; if (chain().findBlock(block_hash, FoundBlock().data(block)) && !block.IsNull()) { LOCK(cs_wallet); next_block = chain().findNextBlock( block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg); if (reorg) { // Abort scan if current block is no longer active, to prevent // marking transactions as coming from the wrong block. // TODO: This should return success instead of failure, see // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 result.last_failed_block = block_hash; result.status = ScanResult::FAILURE; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { CWalletTx::Confirmation confirm(CWalletTx::Status::CONFIRMED, block_height, block_hash, posInBlock); SyncTransaction(block.vtx[posInBlock], confirm, fUpdate); } // scan succeeded, record block as most recent successfully // scanned result.last_scanned_block = block_hash; result.last_scanned_height = block_height; } else { // could not scan block, keep scanning but record this block as // the most recent failure result.last_failed_block = block_hash; result.status = ScanResult::FAILURE; next_block = chain().findNextBlock( block_hash, block_height, FoundBlock().hash(next_block_hash), &reorg); } if (max_height && block_height >= *max_height) { break; } { if (!next_block || reorg) { // break successfully when rescan has reached the tip, or // previous block is no longer on the chain due to a reorg break; } // increment block and verification progress block_hash = next_block_hash; ++block_height; progress_current = chain().guessVerificationProgress(block_hash); // handle updated tip hash const BlockHash prev_tip_hash = tip_hash; tip_hash = WITH_LOCK(cs_wallet, return GetLastBlockHash()); if (!max_height && prev_tip_hash != tip_hash) { // in case the tip has changed, update progress max progress_end = chain().guessVerificationProgress(tip_hash); } } } // Hide progress dialog in GUI. ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); if (block_height && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current); result.status = ScanResult::USER_ABORT; } else if (block_height && chain().shutdownRequested()) { WalletLogPrintf( "Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current); result.status = ScanResult::USER_ABORT; } else { WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); } return result; } void CWallet::ReacceptWalletTransactions() { // If transactions aren't being broadcasted, don't let them into local // mempool either. if (!fBroadcastTransactions) { return; } std::map<int64_t, CWalletTx *> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion // order. for (std::pair<const TxId, CWalletTx> &item : mapWallet) { const TxId &wtxid = item.first; CWalletTx &wtx = item.second; assert(wtx.GetId() == wtxid); int nDepth = wtx.GetDepthInMainChain(); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); } } // Try to add wallet transactions to memory pool. for (const std::pair<const int64_t, CWalletTx *> &item : mapSorted) { CWalletTx &wtx = *(item.second); std::string unused_err_string; wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); } } bool CWalletTx::SubmitMemoryPoolAndRelay(std::string &err_string, bool relay) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) { return false; } // Don't relay abandoned transactions if (isAbandoned()) { return false; } // Don't try to submit coinbase transactions. These would fail anyway but // would cause log spam. if (IsCoinBase()) { return false; } // Don't try to submit conflicted or confirmed transactions. if (GetDepthInMainChain() != 0) { return false; } // Submit transaction to mempool for relay pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetId().ToString()); // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors // because we think that this newly generated transaction's change is // unavailable as we're not yet aware that it is in the mempool. // // Irrespective of the failure reason, un-marking fInMempool // out-of-order is incorrect - it should be unmarked when // TransactionRemovedFromMempool fires. bool ret = pwallet->chain().broadcastTransaction( GetConfig(), tx, pwallet->m_default_max_tx_fee, relay, err_string); fInMempool |= ret; return ret; } std::set<TxId> CWalletTx::GetConflicts() const { std::set<TxId> result; if (pwallet != nullptr) { const TxId &txid = GetId(); result = pwallet->GetConflicts(txid); result.erase(txid); } return result; } Amount CWalletTx::GetCachableAmount(AmountType type, const isminefilter &filter, bool recalculate) const { auto &amount = m_amounts[type]; if (recalculate || !amount.m_cached[filter]) { amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); m_is_cache_empty = false; } return amount.m_value[filter]; } Amount CWalletTx::GetDebit(const isminefilter &filter) const { if (tx->vin.empty()) { return Amount::zero(); } Amount debit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); } return debit; } Amount CWalletTx::GetCredit(const isminefilter &filter) const { // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase()) { return Amount::zero(); } Amount credit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change. credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); } return credit; } Amount CWalletTx::GetImmatureCredit(bool fUseCache) const { if (IsImmatureCoinBase() && IsInMainChain()) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return Amount::zero(); } Amount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter &filter) const { if (pwallet == nullptr) { return Amount::zero(); } // Avoid caching ismine for NO or ALL cases (could remove this check and // simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase()) { return Amount::zero(); } if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { return m_amounts[AVAILABLE_CREDIT].m_value[filter]; } bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); Amount nCredit = Amount::zero(); const TxId &txid = GetId(); for (uint32_t i = 0; i < tx->vout.size(); i++) { if (!pwallet->IsSpent(COutPoint(txid, i)) && (allow_used_addresses || !pwallet->IsSpentKey(txid, i))) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) { throw std::runtime_error(std::string(__func__) + " : value out of range"); } } } if (allow_cache) { m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); m_is_cache_empty = false; } return nCredit; } Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const { if (IsImmatureCoinBase() && IsInMainChain()) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } return Amount::zero(); } Amount CWalletTx::GetChange() const { if (fChangeCached) { return nChangeCached; } nChangeCached = pwallet->GetChange(*tx); fChangeCached = true; return nChangeCached; } bool CWalletTx::InMempool() const { return fInMempool; } bool CWalletTx::IsTrusted() const { std::set<TxId> s; return IsTrusted(s); } bool CWalletTx::IsTrusted(std::set<TxId> &trusted_parents) const { // Quick answer in most cases TxValidationState state; if (!pwallet->chain().contextualCheckTransactionForCurrentBlock(*tx, state)) { return false; } int nDepth = GetDepthInMainChain(); if (nDepth >= 1) { return true; } if (nDepth < 0) { return false; } // using wtx's cached debit if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) { return false; } // Don't trust unconfirmed transactions from us unless they are in the // mempool. if (!InMempool()) { return false; } // Trusted if all inputs are from us and are in the mempool: for (const CTxIn &txin : tx->vin) { // Transactions not sent by us: not trusted const CWalletTx *parent = pwallet->GetWalletTx(txin.prevout.GetTxId()); if (parent == nullptr) { return false; } const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()]; // Check that this specific input being spent is trusted if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) { return false; } // If we've already trusted this parent, continue if (trusted_parents.count(parent->GetId())) { continue; } // Recurse to check that the parent is also trusted if (!parent->IsTrusted(trusted_parents)) { return false; } trusted_parents.insert(parent->GetId()); } return true; } bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const { CMutableTransaction tx1{*this->tx}; CMutableTransaction tx2{*_tx.tx}; for (auto &txin : tx1.vin) { txin.scriptSig = CScript(); } for (auto &txin : tx2.vin) { txin.scriptSig = CScript(); } return CTransaction(tx1) == CTransaction(tx2); } // Rebroadcast transactions from the wallet. We do this on a random timer // to slightly obfuscate which transactions come from our wallet. // // Ideally, we'd only resend transactions that we think should have been // mined in the most recent block. Any transaction that wasn't in the top // blockweight of transactions in the mempool shouldn't have been mined, // and so is probably just sitting in the mempool waiting to be confirmed. // Rebroadcasting does nothing to speed up confirmation and only damages // privacy. void CWallet::ResendWalletTransactions() { // During reindex, importing and IBD, old wallet transactions become // unconfirmed. Don't resend them as that would spam other nodes. if (!chain().isReadyToBroadcast()) { return; } // Do this infrequently and randomly to avoid giving away that these are our // transactions. if (GetTime() < nNextResend || !fBroadcastTransactions) { return; } bool fFirst = (nNextResend == 0); // resend 12-36 hours from now, ~1 day on average. nNextResend = GetTime() + (12 * 60 * 60) + GetRand(24 * 60 * 60); if (fFirst) { return; } int submitted_tx_count = 0; { // cs_wallet scope LOCK(cs_wallet); // Relay transactions for (std::pair<const TxId, CWalletTx> &item : mapWallet) { CWalletTx &wtx = item.second; // Attempt to rebroadcast all txes more than 5 minutes older than // the last block. SubmitMemoryPoolAndRelay() will not rebroadcast // any confirmed or conflicting txs. if (wtx.nTimeReceived > m_best_block_time - 5 * 60) { continue; } std::string unused_err_string; if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) { ++submitted_tx_count; } } } // cs_wallet if (submitted_tx_count > 0) { WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count); } } /** @} */ // end of mapWallet void MaybeResendWalletTxs() { for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) { pwallet->ResendWalletTransactions(); } } /** * @defgroup Actions * * @{ */ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const { Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; LOCK(cs_wallet); std::set<TxId> trusted_parents; for (const auto &entry : mapWallet) { const CWalletTx &wtx = entry.second; const bool is_trusted{wtx.IsTrusted(trusted_parents)}; const int tx_depth{wtx.GetDepthInMainChain()}; const Amount tx_credit_mine{wtx.GetAvailableCredit( /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; const Amount tx_credit_watchonly{wtx.GetAvailableCredit( /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; } if (!is_trusted && tx_depth == 0 && wtx.InMempool()) { ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } ret.m_mine_immature += wtx.GetImmatureCredit(); ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); } return ret; } Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const { LOCK(cs_wallet); Amount balance = Amount::zero(); std::vector<COutput> vCoins; AvailableCoins(vCoins, true, coinControl); for (const COutput &out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; } } return balance; } void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const Amount nMinimumAmount, const Amount nMaximumAmount, const Amount nMinimumSumAmount, const uint64_t nMaximumCount) const { AssertLockHeld(cs_wallet); vCoins.clear(); Amount nTotal = Amount::zero(); // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we // always allow), or we default to avoiding, and only in the case where a // coin control object is provided, and has the avoid address reuse flag set // to false, do we allow already used addresses bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; std::set<TxId> trusted_parents; for (const auto &entry : mapWallet) { const TxId &wtxid = entry.first; const CWalletTx &wtx = entry.second; TxValidationState state; if (!chain().contextualCheckTransactionForCurrentBlock(*wtx.tx, state)) { continue; } if (wtx.IsImmatureCoinBase()) { continue; } int nDepth = wtx.GetDepthInMainChain(); if (nDepth < 0) { continue; } // We should not consider coins which aren't at least in our mempool. // It's possible for these to be conflicted via ancestors which we may // never be able to detect. if (nDepth == 0 && !wtx.InMempool()) { continue; } bool safeTx = wtx.IsTrusted(trusted_parents); // Bitcoin-ABC: Removed check that prevents consideration of coins from // transactions that are replacing other transactions. This check based // on wtx.mapValue.count("replaces_txid") which was not being set // anywhere. // Similarly, we should not consider coins from transactions that have // been replaced. In the example above, we would want to prevent // creation of a transaction A' spending an output of A, because if // transaction B were initially confirmed, conflicting with A and A', we // wouldn't want to the user to create a transaction D intending to // replace A', but potentially resulting in a scenario where A, A', and // D could all be accepted (instead of just B and D, or just A and A' // like the user would want). // Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set // in the wallet code. if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { safeTx = false; } if (fOnlySafe && !safeTx) { continue; } if (nDepth < min_depth || nDepth > max_depth) { continue; } for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) { // Only consider selected coins if add_inputs is false if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) { continue; } if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) { continue; } const COutPoint outpoint(wtxid, i); if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(outpoint)) { continue; } if (IsLockedCoin(outpoint)) { continue; } if (IsSpent(outpoint)) { continue; } isminetype mine = IsMine(wtx.tx->vout[i]); if (mine == ISMINE_NO) { continue; } if (!allow_used_addresses && IsSpentKey(wtxid, i)) { continue; } std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); vCoins.push_back( COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { nTotal += wtx.tx->vout[i].nValue; if (nTotal >= nMinimumSumAmount) { return; } } // Checks the maximum number of UTXO's. if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { return; } } } } std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const { AssertLockHeld(cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; AvailableCoins(availableCoins); for (const auto &coin : availableCoins) { CTxDestination address; if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && ExtractDestination( FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } } std::vector<COutPoint> lockedCoins; ListLockedCoins(lockedCoins); // Include watch-only for LegacyScriptPubKeyMan wallets without private keys const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; for (const auto &output : lockedCoins) { auto it = mapWallet.find(output.GetTxId()); if (it != mapWallet.end()) { int depth = it->second.GetDepthInMainChain(); if (depth >= 0 && output.GetN() < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.GetN()]) == is_mine_filter) { CTxDestination address; if (ExtractDestination( FindNonChangeParentOutput(*it->second.tx, output.GetN()) .scriptPubKey, address)) { result[address].emplace_back( &it->second, output.GetN(), depth, true /* spendable */, true /* solvable */, false /* safe */); } } } } return result; } const CTxOut &CWallet::FindNonChangeParentOutput(const CTransaction &tx, int output) const { const CTransaction *ptx = &tx; int n = output; while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { const COutPoint &prevout = ptx->vin[0].prevout; auto it = mapWallet.find(prevout.GetTxId()); if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.GetN() || !IsMine(it->second.tx->vout[prevout.GetN()])) { break; } ptx = it->second.tx.get(); n = prevout.GetN(); } return ptx->vout[n]; } bool CWallet::SelectCoinsMinConf( const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CoinSelectionParams &coin_selection_params, bool &bnb_used) const { setCoinsRet.clear(); nValueRet = Amount::zero(); std::vector<OutputGroup> utxo_pool; if (coin_selection_params.use_bnb) { // Get long term estimate CCoinControl temp; temp.m_confirm_target = 1008; CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp); // Calculate cost of change Amount cost_of_change = chain().relayDustFee().GetFee( coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee( coin_selection_params.change_output_size); // Filter by the min conf specs and add to utxo_pool and calculate // effective value for (OutputGroup &group : groups) { if (!group.EligibleForSpending(eligibility_filter)) { continue; } group.fee = Amount::zero(); group.long_term_fee = Amount::zero(); group.effective_value = Amount::zero(); for (auto it = group.m_outputs.begin(); it != group.m_outputs.end();) { const CInputCoin &coin = *it; Amount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? Amount::zero() : coin_selection_params.effective_fee.GetFee( coin.m_input_bytes)); // Only include outputs that are positive effective value (i.e. // not dust) if (effective_value > Amount::zero()) { group.fee += coin.m_input_bytes < 0 ? Amount::zero() : coin_selection_params.effective_fee.GetFee( coin.m_input_bytes); group.long_term_fee += coin.m_input_bytes < 0 ? Amount::zero() : long_term_feerate.GetFee(coin.m_input_bytes); if (coin_selection_params.m_subtract_fee_outputs) { group.effective_value += coin.txout.nValue; } else { group.effective_value += effective_value; } ++it; } else { it = group.Discard(coin); } } if (group.effective_value > Amount::zero()) { utxo_pool.push_back(group); } } // Calculate the fees for things that aren't inputs Amount not_input_fees = coin_selection_params.effective_fee.GetFee( coin_selection_params.tx_noinputs_size); bnb_used = true; return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees); } else { // Filter by the min conf specs and add to utxo_pool for (const OutputGroup &group : groups) { if (!group.EligibleForSpending(eligibility_filter)) { continue; } utxo_pool.push_back(group); } bnb_used = false; return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet); } } bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins, const Amount nTargetValue, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CCoinControl &coin_control, CoinSelectionParams &coin_selection_params, bool &bnb_used) const { std::vector<COutput> vCoins(vAvailableCoins); Amount value_to_select = nTargetValue; // Default to bnb was not used. If we use it, we set it later bnb_used = false; // coin control -> return all selected outputs (we want all selected to go // into the transaction for sure) if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) { for (const COutput &out : vCoins) { if (!out.fSpendable) { continue; } nValueRet += out.tx->tx->vout[out.i].nValue; setCoinsRet.insert(out.GetInputCoin()); } return (nValueRet >= nTargetValue); } // Calculate value from preset inputs and store them. std::set<CInputCoin> setPresetCoins; Amount nValueFromPresetInputs = Amount::zero(); std::vector<COutPoint> vPresetInputs; coin_control.ListSelected(vPresetInputs); for (const COutPoint &outpoint : vPresetInputs) { std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(outpoint.GetTxId()); if (it != mapWallet.end()) { const CWalletTx &wtx = it->second; // Clearly invalid input, fail if (wtx.tx->vout.size() <= outpoint.GetN()) { return false; } // Just to calculate the marginal byte size CInputCoin coin(wtx.tx, outpoint.GetN(), wtx.GetSpendSize(outpoint.GetN(), false)); nValueFromPresetInputs += coin.txout.nValue; if (coin.m_input_bytes <= 0) { // Not solvable, can't estimate size for fee return false; } coin.effective_value = coin.txout.nValue - coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); if (coin_selection_params.use_bnb) { value_to_select -= coin.effective_value; } else { value_to_select -= coin.txout.nValue; } setPresetCoins.insert(coin); } else { return false; // TODO: Allow non-wallet inputs } } // Remove preset inputs from vCoins for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) { if (setPresetCoins.count(it->GetInputCoin())) { it = vCoins.erase(it); } else { ++it; } } size_t max_ancestors{0}; size_t max_descendants{0}; chain().getPackageLimits(max_ancestors, max_descendants); bool fRejectLongChains = gArgs.GetBoolArg( "-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); // form groups from remaining coins; note that preset coins will not // automatically have their associated (same address) coins included if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) { // Cases where we have 11+ outputs all pointing to the same destination // may result in privacy leaks as they will potentially be // deterministically sorted. We solve that by explicitly shuffling the // outputs before processing Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); } std::vector<OutputGroup> groups = GroupOutputs( vCoins, !coin_control.m_avoid_partial_spends, max_ancestors); bool res = value_to_select <= Amount::zero() || SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf( value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors / 3), std::min((size_t)4, max_descendants / 3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors / 2, max_descendants / 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors - 1, max_descendants - 1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf( value_to_select, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // Because SelectCoinsMinConf clears the setCoinsRet, we now add the // possible inputs to the coinset. util::insert(setCoinsRet, setPresetCoins); // Add preset inputs to the total value selected. nValueRet += nValueFromPresetInputs; return res; } bool CWallet::SignTransaction(CMutableTransaction &tx) const { AssertLockHeld(cs_wallet); // Build coins map std::map<COutPoint, Coin> coins; for (auto &input : tx.vin) { auto mi = mapWallet.find(input.prevout.GetTxId()); if (mi == mapWallet.end() || input.prevout.GetN() >= mi->second.tx->vout.size()) { return false; } const CWalletTx &wtx = mi->second; coins[input.prevout] = Coin(wtx.tx->vout[input.prevout.GetN()], wtx.m_confirm.block_height, wtx.IsCoinBase()); } std::map<int, std::string> input_errors; return SignTransaction(tx, coins, SigHashType().withForkId(), input_errors); } bool CWallet::SignTransaction(CMutableTransaction &tx, const std::map<COutPoint, Coin> &coins, SigHashType sighash, std::map<int, std::string> &input_errors) const { // Try to sign with all ScriptPubKeyMans for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) { // spk_man->SignTransaction will return true if the transaction is // complete, so we can exit early and return true if that happens if (spk_man->SignTransaction(tx, coins, sighash, input_errors)) { return true; } } // At this point, one input was not fully signed otherwise we would have // exited already Find that input and figure out what went wrong. for (size_t i = 0; i < tx.vin.size(); i++) { // Get the prevout CTxIn &txin = tx.vin[i]; auto coin = coins.find(txin.prevout); if (coin == coins.end() || coin->second.IsSpent()) { input_errors[i] = "Input not found or already spent"; continue; } // Check if this input is complete SignatureData sigdata = DataFromTransaction(tx, i, coin->second.GetTxOut()); if (!sigdata.complete) { input_errors[i] = "Unable to sign input, missing keys"; continue; } } // When there are no available providers for the remaining inputs, use the // legacy provider so we can get proper error messages. auto legacy_spk_man = GetLegacyScriptPubKeyMan(); if (legacy_spk_man && legacy_spk_man->SignTransaction(tx, coins, sighash, input_errors)) { return true; } return false; } TransactionError CWallet::FillPSBT(PartiallySignedTransaction &psbtx, bool &complete, SigHashType sighash_type, bool sign, bool bip32derivs) const { LOCK(cs_wallet); // Get all of the previous transactions for (size_t i = 0; i < psbtx.tx->vin.size(); ++i) { const CTxIn &txin = psbtx.tx->vin[i]; PSBTInput &input = psbtx.inputs.at(i); if (PSBTInputSigned(input)) { continue; } // Verify input looks sane. if (!input.IsSane()) { return TransactionError::INVALID_PSBT; } // If we have no utxo, grab it from the wallet. if (input.utxo.IsNull()) { const TxId &txid = txin.prevout.GetTxId(); const auto it = mapWallet.find(txid); if (it != mapWallet.end()) { const CWalletTx &wtx = it->second; CTxOut utxo = wtx.tx->vout[txin.prevout.GetN()]; // Update UTXOs from the wallet. input.utxo = utxo; } } } // Fill in information from ScriptPubKeyMans for (ScriptPubKeyMan *spk_man : GetAllScriptPubKeyMans()) { TransactionError res = spk_man->FillPSBT(psbtx, sighash_type, sign, bip32derivs); if (res != TransactionError::OK) { return res; } } // Complete if every input is now signed complete = true; for (const auto &input : psbtx.inputs) { complete &= PSBTInputSigned(input); } return TransactionError::OK; } SigningResult CWallet::SignMessage(const std::string &message, const PKHash &pkhash, std::string &str_sig) const { SignatureData sigdata; CScript script_pub_key = GetScriptForDestination(pkhash); for (const auto &spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script_pub_key, sigdata)) { return spk_man_pair.second->SignMessage(message, pkhash, str_sig); } } return SigningResult::PRIVATE_KEY_NOT_AVAILABLE; } bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, bool lockUnspents, const std::set<int> &setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; // Turn the txout set into a CRecipient vector. for (size_t idx = 0; idx < tx.vout.size(); idx++) { const CTxOut &txOut = tx.vout[idx]; CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; vecSend.push_back(recipient); } coinControl.fAllowOtherInputs = true; for (const CTxIn &txin : tx.vin) { coinControl.Select(txin.prevout); } // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). LOCK(cs_wallet); CTransactionRef tx_new; if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, false)) { return false; } if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); } // Copy output sizes from new transaction; they may have had the fee // subtracted from them. for (size_t idx = 0; idx < tx.vout.size(); idx++) { tx.vout[idx].nValue = tx_new->vout[idx].nValue; } // Add new txins (keeping original txin scriptSig/order) for (const CTxIn &txin : tx_new->vin) { if (!coinControl.IsSelected(txin.prevout)) { tx.vin.push_back(txin); if (lockUnspents) { LockCoin(txin.prevout); } } } return true; } static bool IsCurrentForAntiFeeSniping(interfaces::Chain &chain, const BlockHash &block_hash) { if (chain.isInitialBlockDownload()) { return false; } // in seconds constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; int64_t block_time; CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time))); if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { return false; } return true; } /** * Return a height-based locktime for new transactions (uses the height of the * current chain tip unless we are not synced with the current chain */ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain &chain, const BlockHash &block_hash, int block_height) { uint32_t locktime; // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and // the mempool can exceed the cost of deliberately attempting to mine two // blocks to orphan the current best block. By setting nLockTime such that // only the next block can include the transaction, we discourage this // practice as the height restricted and limited blocksize gives miners // considering fee sniping fewer options for pulling off this attack. // // A simple way to think about this is from the wallet's point of view we // always want the blockchain to move forward. By setting nLockTime this // way we're basically making the statement that we only want this // transaction to appear in the next block; we don't want to potentially // encourage reorgs by allowing transactions to appear at lower heights // than the next block in forks of the best chain. // // Of course, the subsidy is high enough, and transaction volume low // enough, that fee sniping isn't a problem yet, but by implementing a fix // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. if (IsCurrentForAntiFeeSniping(chain, block_hash)) { locktime = block_height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, // have better privacy. if (GetRandInt(10) == 0) { locktime = std::max(0, int(locktime) - GetRandInt(100)); } } else { // If our chain is lagging behind, we can't discourage fee sniping nor // help the privacy of high-latency transactions. To avoid leaking a // potentially unique "nLockTime fingerprint", set nLockTime to a // constant. locktime = 0; } assert(locktime < LOCKTIME_THRESHOLD); return locktime; } OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient> &vecSend) { // If -changetype is specified, always use that change type. if (change_type != OutputType::CHANGE_AUTO) { return change_type; } // if m_default_address_type is legacy, use legacy address as change. if (m_default_address_type == OutputType::LEGACY) { return OutputType::LEGACY; } // else use m_default_address_type for change return m_default_address_type; } bool CWallet::CreateTransactionInternal(const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, const CCoinControl &coin_control, bool sign) { Amount nValue = Amount::zero(); const OutputType change_type = TransactionChangeType( coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); ReserveDestination reservedest(this, change_type); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto &recipient : vecSend) { if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) { error = _("Transaction amounts must not be negative"); return false; } nValue += recipient.nAmount; if (recipient.fSubtractFeeFromAmount) { nSubtractFeeFromAmount++; } } if (vecSend.empty()) { error = _("Transaction must have at least one recipient"); return false; } CMutableTransaction txNew; { std::set<CInputCoin> setCoins; LOCK(cs_wallet); txNew.nLockTime = GetLocktimeForNewTransaction( chain(), GetLastBlockHash(), GetLastBlockHeight()); std::vector<COutput> vAvailableCoins; AvailableCoins(vAvailableCoins, true, &coin_control); // Parameters for coin selection, init with dummy CoinSelectionParams coin_selection_params; // Create change script that will be used if we need change // TODO: pass in scriptChange instead of reservedest so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; // coin control: send change to custom address if (!boost::get<CNoDestination>(&coin_control.destChange)) { scriptChange = GetScriptForDestination(coin_control.destChange); // no coin control: send change to newly generated address } else { // Note: We use a new key here to keep it from being obvious // which side is the change. // The drawback is that by not reusing a previous key, the // change may be lost if a backup is restored, if the backup // doesn't have the new private key for the change. If we // reused the old key, it would be possible to add code to look // for and rediscover unknown transactions that were written // with keys of ours to recover post-backup change. // Reserve a new key pair from key pool. If it fails, provide a // dummy destination in case we don't need change. CTxDestination dest; if (!reservedest.GetReservedDestination(dest, true)) { error = _("Transaction needs a change address, but we can't " "generate it. Please call keypoolrefill first."); } scriptChange = GetScriptForDestination(dest); assert(!dest.empty() || scriptChange.empty()); } CTxOut change_prototype_txout(Amount::zero(), scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); // Get the fee rate to use effective values in coin selection CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coin_control); nFeeRet = Amount::zero(); bool pick_new_inputs = true; Amount nValueIn = Amount::zero(); // BnB selector is the only selector used when this is true. // That should only happen on the first pass through the loop. coin_selection_params.use_bnb = true; // If we are doing subtract fee from recipient, don't use effective // values coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // Start with no fee and loop until there is enough fee while (true) { nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); bool fFirst = true; Amount nValueToSelect = nValue; if (nSubtractFeeFromAmount == 0) { nValueToSelect += nFeeRet; } // vouts to the payees if (!coin_selection_params.m_subtract_fee_outputs) { // Static size overhead + outputs vsize. 4 nVersion, 4 // nLocktime, 1 input count, 1 output count coin_selection_params.tx_noinputs_size = 10; } // vouts to the payees for (const auto &recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); if (recipient.fSubtractFeeFromAmount) { assert(nSubtractFeeFromAmount != 0); // Subtract fee equally from each selected recipient. txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount); // First receiver pays the remainder not divisible by output // count. if (fFirst) { fFirst = false; txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount); } } // Include the fee cost for outputs. Note this is only used for // BnB right now if (!coin_selection_params.m_subtract_fee_outputs) { coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); } if (IsDust(txout, chain().relayDustFee())) { if (recipient.fSubtractFeeFromAmount && nFeeRet > Amount::zero()) { if (txout.nValue < Amount::zero()) { error = _("The transaction amount is too small to " "pay the fee"); } else { error = _("The transaction amount is too small to " "send after the fee has been deducted"); } } else { error = _("Transaction amount too small"); } return false; } txNew.vout.push_back(txout); } // Choose coins to use bool bnb_used = false; if (pick_new_inputs) { nValueIn = Amount::zero(); setCoins.clear(); int change_spend_size = CalculateMaximumSignedInputSize( change_prototype_txout, this); // If the wallet doesn't know how to sign change output, assume // p2pkh as lower-bound to allow BnB to do it's thing if (change_spend_size == -1) { coin_selection_params.change_spend_size = DUMMY_P2PKH_INPUT_SIZE; } else { coin_selection_params.change_spend_size = size_t(change_spend_size); } coin_selection_params.effective_fee = nFeeRateNeeded; if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coin_control, coin_selection_params, bnb_used)) { // If BnB was used, it was the first pass. No longer the // first pass and continue loop with knapsack. if (bnb_used) { coin_selection_params.use_bnb = false; continue; } else { error = _("Insufficient funds"); return false; } } } else { bnb_used = false; } const Amount nChange = nValueIn - nValueToSelect; if (nChange > Amount::zero()) { // Fill a vout to ourself. CTxOut newTxOut(nChange, scriptChange); // Never create dust outputs; if we would, just add the dust to // the fee. // The nChange when BnB is used is always going to go to fees. if (IsDust(newTxOut, chain().relayDustFee()) || bnb_used) { nChangePosInOut = -1; nFeeRet += nChange; } else { if (nChangePosInOut == -1) { // Insert change txn at random position: nChangePosInOut = GetRandInt(txNew.vout.size() + 1); } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { error = _("Change index out of range"); return false; } std::vector<CTxOut>::iterator position = txNew.vout.begin() + nChangePosInOut; txNew.vout.insert(position, newTxOut); } } else { nChangePosInOut = -1; } // Dummy fill vin for maximum size estimation // for (const auto &coin : setCoins) { txNew.vin.push_back(CTxIn(coin.outpoint, CScript())); } CTransaction txNewConst(txNew); int nBytes = CalculateMaximumSignedTxSize( txNewConst, this, coin_control.fAllowWatchOnly); if (nBytes < 0) { error = _("Signing transaction failed"); return false; } Amount nFeeNeeded = GetMinimumFee(*this, nBytes, coin_control); if (nFeeRet >= nFeeNeeded) { // Reduce fee to only the needed amount if possible. This // prevents potential overpayment in fees if the coins selected // to meet nFeeNeeded result in a transaction that requires less // fee than the prior iteration. // If we have no change and a big enough excess fee, then try to // construct transaction again only without picking new inputs. // We now know we only need the smaller fee (because of reduced // tx size) and so we should add a change output. Only try this // once. if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { // Add 2 as a buffer in case increasing # of outputs changes // compact size unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; Amount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coin_control); Amount minimum_value_for_change = GetDustThreshold( change_prototype_txout, chain().relayDustFee()); if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) { pick_new_inputs = false; nFeeRet = fee_needed_with_change; continue; } } // If we have change output already, just increase it if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount extraFeePaid = nFeeRet - nFeeNeeded; std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut; change_position->nValue += extraFeePaid; nFeeRet -= extraFeePaid; } // Done, enough fee included. break; } else if (!pick_new_inputs) { // This shouldn't happen, we should have had enough excess fee // to pay for the new output and still meet nFeeNeeded. // Or we should have just subtracted fee from recipients and // nFeeNeeded should not have changed. error = _("Transaction fee and change calculation failed"); return false; } // Try to reduce change to include necessary fee. if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount additionalFeeNeeded = nFeeNeeded - nFeeRet; std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut; // Only reduce change if remaining amount is still a large // enough output. if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) { change_position->nValue -= additionalFeeNeeded; nFeeRet += additionalFeeNeeded; // Done, able to increase fee from change. break; } } // If subtracting fee from recipients, we now know what fee we // need to subtract, we have no reason to reselect inputs. if (nSubtractFeeFromAmount > 0) { pick_new_inputs = false; } // Include more fee and try again. nFeeRet = nFeeNeeded; coin_selection_params.use_bnb = false; continue; } // Give up if change keypool ran out and we failed to find a solution // without change: if (scriptChange.empty() && nChangePosInOut != -1) { return false; } // Shuffle selected coins and fill in final vin txNew.vin.clear(); std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); // Note how the sequence number is set to non-maxint so that // the nLockTime set above actually works. for (const auto &coin : selected_coins) { txNew.vin.push_back( CTxIn(coin.outpoint, CScript(), std::numeric_limits<uint32_t>::max() - 1)); } if (sign && !SignTransaction(txNew)) { error = _("Signing transaction failed"); return false; } // Return the constructed transaction data. tx = MakeTransactionRef(std::move(txNew)); // Limit size. if (tx->GetTotalSize() > MAX_STANDARD_TX_SIZE) { error = _("Transaction too large"); return false; } } if (nFeeRet > m_default_max_tx_fee) { error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); return false; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!chain().checkChainLimits(tx)) { error = _("Transaction has too long of a mempool chain"); return false; } } // Before we return success, we assume any change key will be used to // prevent accidental re-use. reservedest.KeepDestination(); return true; } bool CWallet::CreateTransaction(const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, const CCoinControl &coin_control, bool sign) { int nChangePosIn = nChangePosInOut; CTransactionRef tx2 = tx; bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, sign); // try with avoidpartialspends unless it's enabled already if (res && nFeeRet > Amount::zero() /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > (-1 * SATOSHI) && !coin_control.m_avoid_partial_spends) { CCoinControl tmp_cc = coin_control; tmp_cc.m_avoid_partial_spends = true; Amount nFeeRet2; int nChangePosInOut2 = nChangePosIn; // fired and forgotten; if an error occurs, we discard the results bilingual_str error2; if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, sign)) { // if fee of this alternative one is within the range of the max // fee, we use this one const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee; WalletLogPrintf( "Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); if (use_aps) { tx = tx2; nFeeRet = nFeeRet2; nChangePosInOut = nChangePosInOut2; } } } return res; } void CWallet::CommitTransaction( CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm) { LOCK(cs_wallet); - CWalletTx wtxNew(this, std::move(tx)); - wtxNew.mapValue = std::move(mapValue); - wtxNew.vOrderForm = std::move(orderForm); - wtxNew.fTimeReceivedIsTxTime = true; - wtxNew.fFromMe = true; - - WalletLogPrintfToBeContinued("CommitTransaction:\n%s", - wtxNew.tx->ToString()); + WalletLogPrintfToBeContinued("CommitTransaction:\n%s", tx->ToString()); // Add tx to wallet, because if it has change it's also ours, otherwise just // for transaction history. - AddToWallet(wtxNew); + AddToWallet(tx, {}, [&](CWalletTx &wtx, bool new_tx) { + CHECK_NONFATAL(wtx.mapValue.empty()); + CHECK_NONFATAL(wtx.vOrderForm.empty()); + wtx.mapValue = std::move(mapValue); + wtx.vOrderForm = std::move(orderForm); + wtx.fTimeReceivedIsTxTime = true; + wtx.fFromMe = true; + return true; + }); // Notify that old coins are spent. - for (const CTxIn &txin : wtxNew.tx->vin) { + for (const CTxIn &txin : tx->vin) { CWalletTx &coin = mapWallet.at(txin.prevout.GetTxId()); coin.BindWallet(this); NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED); } // Get the inserted-CWalletTx from mapWallet so that the // fInMempool flag is cached properly - CWalletTx &wtx = mapWallet.at(wtxNew.GetId()); + CWalletTx &wtx = mapWallet.at(tx->GetId()); if (!fBroadcastTransactions) { // Don't submit tx to the mempool return; } std::string err_string; if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast " "immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, instead // delete wtx from the wallet and return failure. } } DBErrors CWallet::LoadWallet(bool &fFirstRunRet) { LOCK(cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this); if (nLoadWalletRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { for (const auto &spk_man_pair : m_spk_managers) { spk_man_pair.second->RewriteDB(); } } } // This wallet is in its first run if there are no ScriptPubKeyMans and it // isn't blank or no privkeys fFirstRunRet = m_spk_managers.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); if (fFirstRunRet) { assert(m_external_spk_managers.empty()); assert(m_internal_spk_managers.empty()); } if (nLoadWalletRet != DBErrors::LOAD_OK) { return nLoadWalletRet; } return DBErrors::LOAD_OK; } DBErrors CWallet::ZapSelectTx(std::vector<TxId> &txIdsIn, std::vector<TxId> &txIdsOut) { AssertLockHeld(cs_wallet); DBErrors nZapSelectTxRet = WalletBatch(*database, "cr+").ZapSelectTx(txIdsIn, txIdsOut); for (const TxId &txid : txIdsOut) { const auto &it = mapWallet.find(txid); wtxOrdered.erase(it->second.m_it_wtxOrdered); mapWallet.erase(it); NotifyTransactionChanged(this, txid, CT_DELETED); } if (nZapSelectTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { for (const auto &spk_man_pair : m_spk_managers) { spk_man_pair.second->RewriteDB(); } } } if (nZapSelectTxRet != DBErrors::LOAD_OK) { return nZapSelectTxRet; } MarkDirty(); return DBErrors::LOAD_OK; } DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx> &vWtx) { DBErrors nZapWalletTxRet = WalletBatch(*database, "cr+").ZapWalletTx(vWtx); if (nZapWalletTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { for (const auto &spk_man_pair : m_spk_managers) { spk_man_pair.second->RewriteDB(); } } } if (nZapWalletTxRet != DBErrors::LOAD_OK) { return nZapWalletTxRet; } return DBErrors::LOAD_OK; } bool CWallet::SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address, const std::string &strName, const std::string &strPurpose) { bool fUpdated = false; { LOCK(cs_wallet); std::map<CTxDestination, CAddressBookData>::iterator mi = m_address_book.find(address); fUpdated = (mi != m_address_book.end() && !mi->second.IsChange()); m_address_book[address].SetLabel(strName); // Update purpose only if requested. if (!strPurpose.empty()) { m_address_book[address].purpose = strPurpose; } } NotifyAddressBookChanged(this, address, strName, IsMine(address) != ISMINE_NO, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW)); if (!strPurpose.empty() && !batch.WritePurpose(address, strPurpose)) { return false; } return batch.WriteName(address, strName); } bool CWallet::SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &strPurpose) { WalletBatch batch(*database); return SetAddressBookWithDB(batch, address, strName, strPurpose); } bool CWallet::DelAddressBook(const CTxDestination &address) { // If we want to delete receiving addresses, we need to take care that // DestData "used" (and possibly newer DestData) gets preserved (and the // "deleted" address transformed into a change entry instead of actually // being deleted) // NOTE: This isn't a problem for sending addresses because they never have // any DestData yet! When adding new DestData, it should be considered here // whether to retain or delete it (or move it?). if (IsMine(address)) { WalletLogPrintf("%s called with IsMine address, NOT SUPPORTED. Please " "report this bug! %s\n", __func__, PACKAGE_BUGREPORT); return false; } { LOCK(cs_wallet); // Delete destdata tuples associated with address for (const std::pair<const std::string, std::string> &item : m_address_book[address].destdata) { WalletBatch(*database).EraseDestData(address, item.first); } m_address_book.erase(address); } NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO, "", CT_DELETED); WalletBatch(*database).ErasePurpose(address); return WalletBatch(*database).EraseName(address); } size_t CWallet::KeypoolCountExternalKeys() const { AssertLockHeld(cs_wallet); unsigned int count = 0; for (auto spk_man : GetActiveScriptPubKeyMans()) { count += spk_man->KeypoolCountExternalKeys(); } return count; } unsigned int CWallet::GetKeyPoolSize() const { AssertLockHeld(cs_wallet); unsigned int count = 0; for (auto spk_man : GetActiveScriptPubKeyMans()) { count += spk_man->GetKeyPoolSize(); } return count; } bool CWallet::TopUpKeyPool(unsigned int kpSize) { LOCK(cs_wallet); bool res = true; for (auto spk_man : GetActiveScriptPubKeyMans()) { res &= spk_man->TopUp(kpSize); } return res; } bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination &dest, std::string &error) { LOCK(cs_wallet); error.clear(); bool result = false; auto spk_man = GetScriptPubKeyMan(type, false /* internal */); if (spk_man) { spk_man->TopUp(); result = spk_man->GetNewDestination(type, dest, error); } else { error = strprintf("Error: No %s addresses available.", FormatOutputType(type)); } if (result) { SetAddressBook(dest, label, "receive"); } return result; } bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination &dest, std::string &error) { LOCK(cs_wallet); error.clear(); ReserveDestination reservedest(this, type); if (!reservedest.GetReservedDestination(dest, true)) { error = _("Error: Keypool ran out, please call keypoolrefill first") .translated; return false; } reservedest.KeepDestination(); return true; } int64_t CWallet::GetOldestKeyPoolTime() const { LOCK(cs_wallet); int64_t oldestKey = std::numeric_limits<int64_t>::max(); for (const auto &spk_man_pair : m_spk_managers) { oldestKey = std::min(oldestKey, spk_man_pair.second->GetOldestKeyPoolTime()); } return oldestKey; } void CWallet::MarkDestinationsDirty( const std::set<CTxDestination> &destinations) { for (auto &entry : mapWallet) { CWalletTx &wtx = entry.second; if (wtx.m_is_cache_empty) { continue; } for (size_t i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination dst; if (ExtractDestination(wtx.tx->vout[i].scriptPubKey, dst) && destinations.count(dst)) { wtx.MarkDirty(); break; } } } } std::map<CTxDestination, Amount> CWallet::GetAddressBalances() const { std::map<CTxDestination, Amount> balances; LOCK(cs_wallet); std::set<TxId> trusted_parents; for (const auto &walletEntry : mapWallet) { const CWalletTx &wtx = walletEntry.second; if (!wtx.IsTrusted(trusted_parents)) { continue; } if (wtx.IsImmatureCoinBase()) { continue; } int nDepth = wtx.GetDepthInMainChain(); if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) { continue; } for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; if (!IsMine(wtx.tx->vout[i])) { continue; } if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) { continue; } Amount n = IsSpent(COutPoint(walletEntry.first, i)) ? Amount::zero() : wtx.tx->vout[i].nValue; if (!balances.count(addr)) { balances[addr] = Amount::zero(); } balances[addr] += n; } } return balances; } std::set<std::set<CTxDestination>> CWallet::GetAddressGroupings() const { AssertLockHeld(cs_wallet); std::set<std::set<CTxDestination>> groupings; std::set<CTxDestination> grouping; for (const auto &walletEntry : mapWallet) { const CWalletTx &wtx = walletEntry.second; if (wtx.tx->vin.size() > 0) { bool any_mine = false; // Group all input addresses with each other. for (const auto &txin : wtx.tx->vin) { CTxDestination address; // If this input isn't mine, ignore it. if (!IsMine(txin)) { continue; } if (!ExtractDestination(mapWallet.at(txin.prevout.GetTxId()) .tx->vout[txin.prevout.GetN()] .scriptPubKey, address)) { continue; } grouping.insert(address); any_mine = true; } // Group change with input addresses. if (any_mine) { for (const auto &txout : wtx.tx->vout) { if (IsChange(txout)) { CTxDestination txoutAddr; if (!ExtractDestination(txout.scriptPubKey, txoutAddr)) { continue; } grouping.insert(txoutAddr); } } } if (grouping.size() > 0) { groupings.insert(grouping); grouping.clear(); } } // Group lone addrs by themselves. for (const auto &txout : wtx.tx->vout) { if (IsMine(txout)) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) { continue; } grouping.insert(address); groupings.insert(grouping); grouping.clear(); } } } // A set of pointers to groups of addresses. std::set<std::set<CTxDestination> *> uniqueGroupings; // Map addresses to the unique group containing it. std::map<CTxDestination, std::set<CTxDestination> *> setmap; for (std::set<CTxDestination> _grouping : groupings) { // Make a set of all the groups hit by this new group. std::set<std::set<CTxDestination> *> hits; std::map<CTxDestination, std::set<CTxDestination> *>::iterator it; for (const CTxDestination &address : _grouping) { if ((it = setmap.find(address)) != setmap.end()) { hits.insert((*it).second); } } // Merge all hit groups into a new single group and delete old groups. std::set<CTxDestination> *merged = new std::set<CTxDestination>(_grouping); for (std::set<CTxDestination> *hit : hits) { merged->insert(hit->begin(), hit->end()); uniqueGroupings.erase(hit); delete hit; } uniqueGroupings.insert(merged); // Update setmap. for (const CTxDestination &element : *merged) { setmap[element] = merged; } } std::set<std::set<CTxDestination>> ret; for (const std::set<CTxDestination> *uniqueGrouping : uniqueGroupings) { ret.insert(*uniqueGrouping); delete uniqueGrouping; } return ret; } std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string &label) const { LOCK(cs_wallet); std::set<CTxDestination> result; for (const std::pair<const CTxDestination, CAddressBookData> &item : m_address_book) { if (item.second.IsChange()) { continue; } const CTxDestination &address = item.first; const std::string &strName = item.second.GetLabel(); if (strName == label) { result.insert(address); } } return result; } bool ReserveDestination::GetReservedDestination(CTxDestination &dest, bool internal) { m_spk_man = pwallet->GetScriptPubKeyMan(type, internal); if (!m_spk_man) { return false; } if (nIndex == -1) { m_spk_man->TopUp(); CKeyPool keypool; if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { return false; } fInternal = keypool.fInternal; } dest = address; return true; } void ReserveDestination::KeepDestination() { if (nIndex != -1) { m_spk_man->KeepDestination(nIndex, type); } nIndex = -1; address = CNoDestination(); } void ReserveDestination::ReturnDestination() { if (nIndex != -1) { m_spk_man->ReturnDestination(nIndex, fInternal, address); } nIndex = -1; address = CNoDestination(); } void CWallet::LockCoin(const COutPoint &output) { AssertLockHeld(cs_wallet); setLockedCoins.insert(output); } void CWallet::UnlockCoin(const COutPoint &output) { AssertLockHeld(cs_wallet); setLockedCoins.erase(output); } void CWallet::UnlockAllCoins() { AssertLockHeld(cs_wallet); setLockedCoins.clear(); } bool CWallet::IsLockedCoin(const COutPoint &outpoint) const { AssertLockHeld(cs_wallet); return setLockedCoins.count(outpoint) > 0; } void CWallet::ListLockedCoins(std::vector<COutPoint> &vOutpts) const { AssertLockHeld(cs_wallet); for (COutPoint outpoint : setLockedCoins) { vOutpts.push_back(outpoint); } } /** @} */ // end of Actions void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); mapKeyBirth.clear(); LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan(); assert(spk_man != nullptr); LOCK(spk_man->cs_KeyStore); // Get birth times for keys with metadata. for (const auto &entry : spk_man->mapKeyMetadata) { if (entry.second.nCreateTime) { mapKeyBirth[entry.first] = entry.second.nCreateTime; } } // map in which we'll infer heights of other keys std::map<CKeyID, const CWalletTx::Confirmation *> mapKeyFirstBlock; CWalletTx::Confirmation max_confirm; // the tip can be reorganized; use a 144-block safety margin max_confirm.block_height = GetLastBlockHeight() > 144 ? GetLastBlockHeight() - 144 : 0; CHECK_NONFATAL(chain().findAncestorByHeight( GetLastBlockHash(), max_confirm.block_height, FoundBlock().hash(max_confirm.hashBlock))); for (const CKeyID &keyid : spk_man->GetKeys()) { if (mapKeyBirth.count(keyid) == 0) { mapKeyFirstBlock[keyid] = &max_confirm; } } // If there are no such keys, we're done. if (mapKeyFirstBlock.empty()) { return; } // Find first block that affects those keys, if there are any left. for (const auto &entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; if (wtx.m_confirm.status == CWalletTx::CONFIRMED) { // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // Iterate over all their outputs... for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *spk_man)) { // ... and all their affected keys. auto rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && wtx.m_confirm.block_height < rit->second->block_height) { rit->second = &wtx.m_confirm; } } } } } // Extract block timestamps for those keys. for (const auto &entry : mapKeyFirstBlock) { int64_t block_time; CHECK_NONFATAL(chain().findBlock(entry.second->hashBlock, FoundBlock().time(block_time))); // block times can be 2h off mapKeyBirth[entry.first] = block_time - TIMESTAMP_WINDOW; } } /** * Compute smart timestamp for a transaction being added to the wallet. * * Logic: * - If sending a transaction, assign its timestamp to the current time. * - If receiving a transaction outside a block, assign its timestamp to the * current time. * - If receiving a block with a future timestamp, assign all its (not already * known) transactions' timestamps to the current time. * - If receiving a block with a past timestamp, before the most recent known * transaction (that we care about), assign all its (not already known) * transactions' timestamps to the same timestamp as that most-recent-known * transaction. * - If receiving a block with a past timestamp, but after the most recent known * transaction, assign all its (not already known) transactions' timestamps to * the block time. * * For more information see CWalletTx::nTimeSmart, * https://bitcointalk.org/?topic=54527, or * https://github.com/bitcoin/bitcoin/pull/1393. */ unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.isUnconfirmed() && !wtx.isAbandoned()) { int64_t blocktime; if (chain().findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(blocktime))) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; // Tolerate times up to the last timestamp in the wallet not more // than 5 minutes into the future int64_t latestTolerated = latestNow + 300; const TxItems &txOrdered = wtxOrdered; for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = it->second; if (pwtx == &wtx) { continue; } int64_t nSmartTime; nSmartTime = pwtx->nTimeSmart; if (!nSmartTime) { nSmartTime = pwtx->nTimeReceived; } if (nSmartTime <= latestTolerated) { latestEntry = nSmartTime; if (nSmartTime > latestNow) { latestNow = nSmartTime; } break; } } nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetId().ToString(), wtx.m_confirm.hashBlock.ToString()); } } return nTimeSmart; } bool CWallet::AddDestData(WalletBatch &batch, const CTxDestination &dest, const std::string &key, const std::string &value) { if (boost::get<CNoDestination>(&dest)) { return false; } m_address_book[dest].destdata.insert(std::make_pair(key, value)); return batch.WriteDestData(dest, key, value); } bool CWallet::EraseDestData(WalletBatch &batch, const CTxDestination &dest, const std::string &key) { if (!m_address_book[dest].destdata.erase(key)) { return false; } return batch.EraseDestData(dest, key); } void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { m_address_book[dest].destdata.insert(std::make_pair(key, value)); } bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const { std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest); if (i != m_address_book.end()) { CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); if (j != i->second.destdata.end()) { if (value) { *value = j->second; } return true; } } return false; } std::vector<std::string> CWallet::GetDestValues(const std::string &prefix) const { std::vector<std::string> values; for (const auto &address : m_address_book) { for (const auto &data : address.second.destdata) { if (!data.first.compare(0, prefix.size(), prefix)) { values.emplace_back(data.second); } } } return values; } bool CWallet::Verify(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error_string, std::vector<bilingual_str> &warnings) { // Do some checking on wallet path. It should be either a: // // 1. Path where a directory can be created. // 2. Path to an existing directory. // 3. Path to a symlink to a directory. // 4. For backwards compatibility, the name of a data file in -walletdir. LOCK(cs_wallets); const fs::path &wallet_path = location.GetPath(); fs::file_type path_type = fs::symlink_status(wallet_path).type(); if (!(path_type == fs::file_not_found || path_type == fs::directory_file || (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || (path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) { error_string = Untranslated( strprintf("Invalid -wallet path '%s'. -wallet path should point to " "a directory where wallet.dat and " "database/log.?????????? files can be stored, a location " "where such a directory could be created, " "or (for backwards compatibility) the name of an " "existing data file in -walletdir (%s)", location.GetName(), GetWalletDir())); return false; } // Make sure that the wallet path doesn't clash with an existing wallet path if (IsWalletLoaded(wallet_path)) { error_string = Untranslated(strprintf( "Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName())); return false; } // Keep same database environment instance across Verify/Recover calls // below. std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path); try { return database->Verify(error_string); } catch (const fs::filesystem_error &e) { error_string = Untranslated( strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e))); return false; } } std::shared_ptr<CWallet> CWallet::CreateWalletFromFile( const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error, std::vector<bilingual_str> &warnings, uint64_t wallet_creation_flags) { const std::string walletFile = WalletDataFilePath(location.GetPath()).string(); // Needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; if (gArgs.GetBoolArg("-zapwallettxes", false)) { chain.initMessage( _("Zapping all transactions from wallet...").translated); std::unique_ptr<CWallet> tempWallet = std::make_unique<CWallet>( &chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile); return nullptr; } } chain.initMessage(_("Loading wallet...").translated); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. std::shared_ptr<CWallet> walletInstance( new CWallet(&chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { error = strprintf(_("Error loading %s: Wallet corrupted"), walletFile); return nullptr; } if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { warnings.push_back( strprintf(_("Error reading %s! All keys read correctly, but " "transaction data or address book entries might be " "missing or incorrect."), walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { error = strprintf( _("Error loading %s: Wallet requires newer version of %s"), walletFile, PACKAGE_NAME); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { error = strprintf( _("Wallet needed to be rewritten: restart %s to complete"), PACKAGE_NAME); return nullptr; } else { error = strprintf(_("Error loading %s"), walletFile); return nullptr; } } if (fFirstRun) { // Ensure this wallet.dat can only be opened by clients supporting // HD with chain split and expects no default key. walletInstance->SetMinVersion(FEATURE_LATEST); walletInstance->SetWalletFlags(wallet_creation_flags, false); // Only create LegacyScriptPubKeyMan when not descriptor wallet if (!walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { walletInstance->SetupLegacyScriptPubKeyMan(); } if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { LOCK(walletInstance->cs_wallet); if (walletInstance->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { walletInstance->SetupDescriptorScriptPubKeyMans(); // SetupDescriptorScriptPubKeyMans already calls SetupGeneration // for us so we don't need to call SetupGeneration separately } else { // Legacy wallets need SetupGeneration here. for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (!spk_man->SetupGeneration()) { error = _("Unable to generate initial keys"); return nullptr; } } } } walletInstance->chainStateFlushed(chain.getTipLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation error = strprintf(_("Error loading %s: Private keys can only be " "disabled during creation"), walletFile); return nullptr; } else if (walletInstance->IsWalletFlagSet( WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { for (auto spk_man : walletInstance->GetActiveScriptPubKeyMans()) { if (spk_man->HavePrivateKeys()) { warnings.push_back( strprintf(_("Warning: Private keys detected in wallet {%s} " "with disabled private keys"), walletFile)); } } } if (gArgs.IsArgSet("-mintxfee")) { Amount n = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || n == Amount::zero()) { error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", "")); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { warnings.push_back(AmountHighWarn("-mintxfee") + Untranslated(" ") + _("This is the minimum transaction fee you pay " "on every transaction.")); } walletInstance->m_min_fee = CFeeRate(n); } if (gArgs.IsArgSet("-maxapsfee")) { Amount n = Amount::zero(); if (gArgs.GetArg("-maxapsfee", "") == "-1") { n = -1 * SATOSHI; } else if (!ParseMoney(gArgs.GetArg("-maxapsfee", ""), n)) { error = AmountErrMsg("maxapsfee", gArgs.GetArg("-maxapsfee", "")); return nullptr; } if (n > HIGH_APS_FEE) { warnings.push_back( AmountHighWarn("-maxapsfee") + Untranslated(" ") + _("This is the maximum transaction fee you pay to prioritize " "partial spend avoidance over regular coin selection.")); } walletInstance->m_max_aps_fee = n; } if (gArgs.IsArgSet("-fallbackfee")) { Amount nFeePerK = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { error = strprintf(_("Invalid amount for -fallbackfee=<amount>: '%s'"), gArgs.GetArg("-fallbackfee", "")); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { warnings.push_back(AmountHighWarn("-fallbackfee") + Untranslated(" ") + _("This is the transaction fee you may pay when " "fee estimates are not available.")); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); } // Disable fallback fee in case value was set to 0, enable if non-null value walletInstance->m_allow_fallback_fee = walletInstance->m_fallback_fee.GetFeePerK() != Amount::zero(); if (gArgs.IsArgSet("-paytxfee")) { Amount nFeePerK = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { error = AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", "")); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { warnings.push_back(AmountHighWarn("-paytxfee") + Untranslated(" ") + _("This is the transaction fee you will pay if " "you send a transaction.")); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) { error = strprintf(_("Invalid amount for -paytxfee=<amount>: '%s' " "(must be at least %s)"), gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString()); return nullptr; } } if (gArgs.IsArgSet("-maxtxfee")) { Amount nMaxFee = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { error = AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", "")); return nullptr; } if (nMaxFee > HIGH_MAX_TX_FEE) { warnings.push_back(_("-maxtxfee is set very high! Fees this large " "could be paid on a single transaction.")); } if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) { error = strprintf( _("Invalid amount for -maxtxfee=<amount>: '%s' (must be at " "least the minrelay fee of %s to prevent stuck " "transactions)"), gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString()); return nullptr; } walletInstance->m_default_max_tx_fee = nMaxFee; } if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) { warnings.push_back( AmountHighWarn("-minrelaytxfee") + Untranslated(" ") + _("The wallet will avoid paying less than the minimum relay fee.")); } walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_default_address_type = DEFAULT_ADDRESS_TYPE; walletInstance->m_default_change_type = DEFAULT_CHANGE_TYPE; walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); LOCK(walletInstance->cs_wallet); // Register wallet with validationinterface. It's done before rescan to // avoid missing block connections between end of rescan and validation // subscribing. Because of wallet lock being hold, block connection // notifications are going to be pending on the validation-side until lock // release. It's likely to have block processing duplicata (if rescan block // range overlaps with notification one) but we guarantee at least than // wallet state is correct after notifications delivery. This is temporary // until rescan and notifications delivery are unified under same interface. walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); int rescan_height = 0; if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; if (batch.ReadBestBlock(locator)) { if (const std::optional<int> fork_height = chain.findLocatorFork(locator)) { rescan_height = *fork_height; } } } const std::optional<int> tip_height = chain.getHeight(); if (tip_height) { walletInstance->m_last_block_processed = chain.getBlockHash(*tip_height); walletInstance->m_last_block_processed_height = *tip_height; } else { walletInstance->m_last_block_processed.SetNull(); walletInstance->m_last_block_processed_height = -1; } if (tip_height && *tip_height != rescan_height) { // We can't rescan beyond non-pruned blocks, stop and throw an error. // This might happen if a user uses an old wallet within a pruned node // or if they ran -disablewallet for a longer time, then decided to // re-enable if (chain.havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will load the wallet, // but fail the rescan with a generic error. int block_height = *tip_height; while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { --block_height; } if (rescan_height != block_height) { error = _("Prune: last wallet synchronisation goes beyond " "pruned data. You need to -reindex (download the " "whole blockchain again in case of pruned node)"); return nullptr; } } chain.initMessage(_("Rescanning...").translated); walletInstance->WalletLogPrintf( "Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height); // No need to read and scan block if block was created before our wallet // birthday (as adjusted for block time variability) std::optional<int64_t> time_first_key; for (auto spk_man : walletInstance->GetAllScriptPubKeyMans()) { int64_t time = spk_man->GetTimeFirstKey(); if (!time_first_key || time < *time_first_key) { time_first_key = time; } } if (time_first_key) { if (std::optional<int> first_block = chain.findFirstBlockWithTimeAndHeight( *time_first_key - TIMESTAMP_WINDOW, rescan_height, nullptr)) { rescan_height = *first_block; } } { WalletRescanReserver reserver(*walletInstance); if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance ->ScanForWalletTransactions( chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */) .status)) { error = _("Failed to rescan the wallet during initialization"); return nullptr; } } walletInstance->chainStateFlushed(chain.getTipLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") { WalletBatch batch(*walletInstance->database); for (const CWalletTx &wtxOld : vWtx) { const TxId txid = wtxOld.GetId(); std::map<TxId, CWalletTx>::iterator mi = walletInstance->mapWallet.find(txid); if (mi != walletInstance->mapWallet.end()) { const CWalletTx *copyFrom = &wtxOld; CWalletTx *copyTo = &mi->second; copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; copyTo->nTimeReceived = copyFrom->nTimeReceived; copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; copyTo->nOrderPos = copyFrom->nOrderPos; batch.WriteTx(*copyTo); } } } } { LOCK(cs_wallets); for (auto &load_wallet : g_load_wallet_fns) { load_wallet(interfaces::MakeWallet(walletInstance)); } } walletInstance->SetBroadcastTransactions( gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); walletInstance->WalletLogPrintf("m_address_book.size() = %u\n", walletInstance->m_address_book.size()); return walletInstance; } const CAddressBookData * CWallet::FindAddressBookEntry(const CTxDestination &dest, bool allow_change) const { const auto &address_book_it = m_address_book.find(dest); if (address_book_it == m_address_book.end()) { return nullptr; } if ((!allow_change) && address_book_it->second.IsChange()) { return nullptr; } return &address_book_it->second; } bool CWallet::UpgradeWallet(int version, bilingual_str &error, std::vector<bilingual_str> &warnings) { int prev_version = GetVersion(); int nMaxVersion = version; // The -upgradewallet without argument case if (nMaxVersion == 0) { WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = FEATURE_LATEST; // permanently upgrade the wallet immediately SetMinVersion(FEATURE_LATEST); } else { WalletLogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion); } if (nMaxVersion < GetVersion()) { error = _("Cannot downgrade wallet"); return false; } SetMaxVersion(nMaxVersion); LOCK(cs_wallet); // Do not upgrade versions to any version between HD_SPLIT and // FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT int max_version = GetVersion(); if (!CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { error = _("Cannot upgrade a non HD split wallet without upgrading to " "support pre split keypool. Please use version 200300 or no " "version specified."); return false; } for (auto spk_man : GetActiveScriptPubKeyMans()) { if (!spk_man->Upgrade(prev_version, error)) { return false; } } return true; } void CWallet::postInitProcess() { LOCK(cs_wallet); // Add wallet transactions that aren't already in a block to mempool. // Do this here as mempool requires genesis block to be loaded. ReacceptWalletTransactions(); // Update wallet transactions with current mempool transactions. chain().requestMempoolTransactions(*this); } bool CWallet::BackupWallet(const std::string &strDest) const { return database->Backup(strDest); } CKeyPool::CKeyPool() { nTime = GetTime(); fInternal = false; m_pre_split = false; } CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; fInternal = internalIn; m_pre_split = false; } int CWalletTx::GetDepthInMainChain() const { assert(pwallet != nullptr); AssertLockHeld(pwallet->cs_wallet); if (isUnconfirmed() || isAbandoned()) { return 0; } return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); } int CWalletTx::GetBlocksToMaturity() const { if (!IsCoinBase()) { return 0; } int chain_depth = GetDepthInMainChain(); // coinbase tx should not be conflicted assert(chain_depth >= 0); return std::max(0, (COINBASE_MATURITY + 1) - chain_depth); } bool CWalletTx::IsImmatureCoinBase() const { // note GetBlocksToMaturity is 0 for non-coinbase tx return GetBlocksToMaturity() > 0; } std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput> &outputs, bool single_coin, const size_t max_ancestors) const { std::vector<OutputGroup> groups; std::map<CTxDestination, OutputGroup> gmap; std::set<CTxDestination> full_groups; for (const auto &output : outputs) { if (output.fSpendable) { CTxDestination dst; CInputCoin input_coin = output.GetInputCoin(); size_t ancestors, descendants; chain().getTransactionAncestry(output.tx->GetId(), ancestors, descendants); if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) { auto it = gmap.find(dst); if (it != gmap.end()) { // Limit output groups to no more than // OUTPUT_GROUP_MAX_ENTRIES number of entries, to protect // against inadvertently creating a too-large transaction // when using -avoidpartialspends to prevent breaking // consensus or surprising users with a very high amount of // fees. if (it->second.m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) { groups.push_back(it->second); it->second = OutputGroup{}; full_groups.insert(dst); } it->second.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); } else { gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); } } else { groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); } } } if (!single_coin) { for (auto &it : gmap) { auto &group = it.second; if (full_groups.count(it.first) > 0) { // Make this unattractive as we want coin selection to avoid it // if possible group.m_ancestors = max_ancestors - 1; } groups.push_back(group); } } return groups; } bool CWallet::IsCrypted() const { return HasEncryptionKeys(); } bool CWallet::IsLocked() const { if (!IsCrypted()) { return false; } LOCK(cs_wallet); return vMasterKey.empty(); } bool CWallet::Lock() { if (!IsCrypted()) { return false; } { LOCK(cs_wallet); vMasterKey.clear(); } NotifyStatusChanged(this); return true; } bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) { { LOCK(cs_wallet); for (const auto &spk_man_pair : m_spk_managers) { if (!spk_man_pair.second->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) { return false; } } vMasterKey = vMasterKeyIn; } NotifyStatusChanged(this); return true; } std::set<ScriptPubKeyMan *> CWallet::GetActiveScriptPubKeyMans() const { std::set<ScriptPubKeyMan *> spk_mans; for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { auto spk_man = GetScriptPubKeyMan(t, internal); if (spk_man) { spk_mans.insert(spk_man); } } } return spk_mans; } std::set<ScriptPubKeyMan *> CWallet::GetAllScriptPubKeyMans() const { std::set<ScriptPubKeyMan *> spk_mans; for (const auto &spk_man_pair : m_spk_managers) { spk_mans.insert(spk_man_pair.second.get()); } return spk_mans; } ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const OutputType &type, bool internal) const { const std::map<OutputType, ScriptPubKeyMan *> &spk_managers = internal ? m_internal_spk_managers : m_external_spk_managers; std::map<OutputType, ScriptPubKeyMan *>::const_iterator it = spk_managers.find(type); if (it == spk_managers.end()) { WalletLogPrintf( "%s scriptPubKey Manager for output type %d does not exist\n", internal ? "Internal" : "External", static_cast<int>(type)); return nullptr; } return it->second; } std::set<ScriptPubKeyMan *> CWallet::GetScriptPubKeyMans(const CScript &script, SignatureData &sigdata) const { std::set<ScriptPubKeyMan *> spk_mans; for (const auto &spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script, sigdata)) { spk_mans.insert(spk_man_pair.second.get()); } } return spk_mans; } ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const CScript &script) const { SignatureData sigdata; for (const auto &spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script, sigdata)) { return spk_man_pair.second.get(); } } return nullptr; } ScriptPubKeyMan *CWallet::GetScriptPubKeyMan(const uint256 &id) const { if (m_spk_managers.count(id) > 0) { return m_spk_managers.at(id).get(); } return nullptr; } std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript &script) const { SignatureData sigdata; return GetSolvingProvider(script, sigdata); } std::unique_ptr<SigningProvider> CWallet::GetSolvingProvider(const CScript &script, SignatureData &sigdata) const { for (const auto &spk_man_pair : m_spk_managers) { if (spk_man_pair.second->CanProvide(script, sigdata)) { return spk_man_pair.second->GetSolvingProvider(script); } } return nullptr; } LegacyScriptPubKeyMan *CWallet::GetLegacyScriptPubKeyMan() const { if (IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { return nullptr; } // Legacy wallets only have one ScriptPubKeyMan which is a // LegacyScriptPubKeyMan. Everything in m_internal_spk_managers and // m_external_spk_managers point to the same legacyScriptPubKeyMan. auto it = m_internal_spk_managers.find(OutputType::LEGACY); if (it == m_internal_spk_managers.end()) { return nullptr; } return dynamic_cast<LegacyScriptPubKeyMan *>(it->second); } LegacyScriptPubKeyMan *CWallet::GetOrCreateLegacyScriptPubKeyMan() { SetupLegacyScriptPubKeyMan(); return GetLegacyScriptPubKeyMan(); } void CWallet::SetupLegacyScriptPubKeyMan() { if (!m_internal_spk_managers.empty() || !m_external_spk_managers.empty() || !m_spk_managers.empty() || IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { return; } auto spk_manager = std::unique_ptr<ScriptPubKeyMan>(new LegacyScriptPubKeyMan(*this)); for (const auto &type : OUTPUT_TYPES) { m_internal_spk_managers[type] = spk_manager.get(); m_external_spk_managers[type] = spk_manager.get(); } m_spk_managers[spk_manager->GetID()] = std::move(spk_manager); } const CKeyingMaterial &CWallet::GetEncryptionKey() const { return vMasterKey; } bool CWallet::HasEncryptionKeys() const { return !mapMasterKeys.empty(); } void CWallet::ConnectScriptPubKeyManNotifiers() { for (const auto &spk_man : GetActiveScriptPubKeyMans()) { spk_man->NotifyWatchonlyChanged.connect(NotifyWatchonlyChanged); spk_man->NotifyCanGetAddressesChanged.connect( NotifyCanGetAddressesChanged); } } void CWallet::LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor &desc) { auto spk_manager = std::unique_ptr<ScriptPubKeyMan>( new DescriptorScriptPubKeyMan(*this, desc)); m_spk_managers[id] = std::move(spk_manager); } void CWallet::SetupDescriptorScriptPubKeyMans() { AssertLockHeld(cs_wallet); // Make a seed CKey seed_key; seed_key.MakeNewKey(true); CPubKey seed = seed_key.GetPubKey(); assert(seed_key.VerifyPubKey(seed)); // Get the extended key CExtKey master_key; master_key.SetSeed(seed_key.begin(), seed_key.size()); for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { auto spk_manager = std::make_unique<DescriptorScriptPubKeyMan>(*this, t, internal); if (IsCrypted()) { if (IsLocked()) { throw std::runtime_error( std::string(__func__) + ": Wallet is locked, cannot setup new descriptors"); } if (!spk_manager->CheckDecryptionKey(vMasterKey) && !spk_manager->Encrypt(vMasterKey, nullptr)) { throw std::runtime_error( std::string(__func__) + ": Could not encrypt new descriptors"); } } spk_manager->SetupDescriptorGeneration(master_key); uint256 id = spk_manager->GetID(); m_spk_managers[id] = std::move(spk_manager); SetActiveScriptPubKeyMan(id, t, internal); } } } void CWallet::SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly) { WalletLogPrintf( "Setting spkMan to active: id = %s, type = %d, internal = %d\n", id.ToString(), static_cast<int>(type), static_cast<int>(internal)); auto &spk_mans = internal ? m_internal_spk_managers : m_external_spk_managers; auto spk_man = m_spk_managers.at(id).get(); spk_man->SetType(type, internal); spk_mans[type] = spk_man; if (!memonly) { WalletBatch batch(*database); if (!batch.WriteActiveScriptPubKeyMan(static_cast<uint8_t>(type), id, internal)) { throw std::runtime_error( std::string(__func__) + ": writing active ScriptPubKeyMan id failed"); } } NotifyCanGetAddressesChanged(); } bool CWallet::IsLegacy() const { if (m_internal_spk_managers.count(OutputType::LEGACY) == 0) { return false; } auto spk_man = dynamic_cast<LegacyScriptPubKeyMan *>( m_internal_spk_managers.at(OutputType::LEGACY)); return spk_man != nullptr; } DescriptorScriptPubKeyMan * CWallet::GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const { for (auto &spk_man_pair : m_spk_managers) { // Try to downcast to DescriptorScriptPubKeyMan then check if the // descriptors match DescriptorScriptPubKeyMan *spk_manager = dynamic_cast<DescriptorScriptPubKeyMan *>( spk_man_pair.second.get()); if (spk_manager != nullptr && spk_manager->HasWalletDescriptor(desc)) { return spk_manager; } } return nullptr; } ScriptPubKeyMan * CWallet::AddWalletDescriptor(WalletDescriptor &desc, const FlatSigningProvider &signing_provider, const std::string &label) { if (!IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) { WalletLogPrintf( "Cannot add WalletDescriptor to a non-descriptor wallet\n"); return nullptr; } LOCK(cs_wallet); auto new_spk_man = std::make_unique<DescriptorScriptPubKeyMan>(*this, desc); // If we already have this descriptor, remove it from the maps but add the // existing cache to desc auto old_spk_man = GetDescriptorScriptPubKeyMan(desc); if (old_spk_man) { WalletLogPrintf("Update existing descriptor: %s\n", desc.descriptor->ToString()); { LOCK(old_spk_man->cs_desc_man); new_spk_man->SetCache(old_spk_man->GetWalletDescriptor().cache); } // Remove from maps of active spkMans auto old_spk_man_id = old_spk_man->GetID(); for (bool internal : {false, true}) { for (OutputType t : OUTPUT_TYPES) { auto active_spk_man = GetScriptPubKeyMan(t, internal); if (active_spk_man && active_spk_man->GetID() == old_spk_man_id) { if (internal) { m_internal_spk_managers.erase(t); } else { m_external_spk_managers.erase(t); } break; } } } m_spk_managers.erase(old_spk_man_id); } // Add the private keys to the descriptor for (const auto &entry : signing_provider.keys) { const CKey &key = entry.second; new_spk_man->AddDescriptorKey(key, key.GetPubKey()); } // Top up key pool, the manager will generate new scriptPubKeys internally new_spk_man->TopUp(); // Apply the label if necessary // Note: we disable labels for ranged descriptors if (!desc.descriptor->IsRange()) { auto script_pub_keys = new_spk_man->GetScriptPubKeys(); if (script_pub_keys.empty()) { WalletLogPrintf( "Could not generate scriptPubKeys (cache is empty)\n"); return nullptr; } CTxDestination dest; if (ExtractDestination(script_pub_keys.at(0), dest)) { SetAddressBook(dest, label, "receive"); } } // Save the descriptor to memory auto ret = new_spk_man.get(); m_spk_managers[new_spk_man->GetID()] = std::move(new_spk_man); // Save the descriptor to DB ret->WriteDescriptor(); return ret; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 683e98e1a..93be190cc 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1,1597 +1,1611 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2018-2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_WALLET_H #define BITCOIN_WALLET_WALLET_H #include <amount.h> #include <interfaces/chain.h> #include <interfaces/handler.h> #include <outputtype.h> #include <primitives/blockhash.h> #include <psbt.h> #include <tinyformat.h> #include <util/message.h> #include <util/strencodings.h> #include <util/string.h> #include <util/system.h> #include <util/translation.h> #include <util/ui_change_type.h> #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> #include <wallet/rpcwallet.h> #include <wallet/scriptpubkeyman.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> #include <algorithm> #include <atomic> #include <cstdint> #include <map> #include <memory> #include <set> #include <stdexcept> #include <string> #include <utility> #include <vector> #include <boost/signals2/signal.hpp> using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>; struct bilingual_str; //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. //! Note that, when blocking is not required, the wallet is implicitly unloaded //! by the shared pointer deleter. void UnloadWallet(std::shared_ptr<CWallet> &&wallet); bool AddWallet(const std::shared_ptr<CWallet> &wallet); bool RemoveWallet(const std::shared_ptr<CWallet> &wallet); bool HasWallets(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string &name); std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error, std::vector<bilingual_str> &warnings); std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet); enum class WalletCreationStatus { SUCCESS, CREATION_FAILED, ENCRYPTION_FAILED }; WalletCreationStatus CreateWallet(const CChainParams ¶ms, interfaces::Chain &chain, const SecureString &passphrase, uint64_t wallet_creation_flags, const std::string &name, bilingual_str &error, std::vector<bilingual_str> &warnings, std::shared_ptr<CWallet> &result); //! -paytxfee default constexpr Amount DEFAULT_PAY_TX_FEE = Amount::zero(); //! -fallbackfee default static const Amount DEFAULT_FALLBACK_FEE = Amount::zero(); //! -mintxfee default static const Amount DEFAULT_TRANSACTION_MINFEE_PER_KB = 1000 * SATOSHI; /** * maximum fee increase allowed to do partial spend avoidance, even for nodes * with this feature disabled by default * * A value of -1 disables this feature completely. * A value of 0 (current default) means to attempt to do partial spend * avoidance, and use its results if the fees remain *unchanged* A value > 0 * means to do partial spend avoidance if the fee difference against a regular * coin selection instance is in the range [0..value]. */ static const Amount DEFAULT_MAX_AVOIDPARTIALSPEND_FEE = Amount::zero(); //! discourage APS fee higher than this amount constexpr Amount HIGH_APS_FEE{COIN / 10000}; //! minimum recommended increment for BIP 125 replacement txs static const Amount WALLET_INCREMENTAL_RELAY_FEE(5000 * SATOSHI); //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; //! -maxtxfee default constexpr Amount DEFAULT_TRANSACTION_MAXFEE{COIN / 10}; //! Discourage users to set fees higher than this amount (in satoshis) per kB constexpr Amount HIGH_TX_FEE_PER_KB{COIN / 100}; //! -maxtxfee will warn if called with a higher fee than this amount (in //! satoshis) constexpr Amount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB}; //! Pre-calculated constants for input size estimation static constexpr size_t DUMMY_P2PKH_INPUT_SIZE = 148; class CChainParams; class CCoinControl; class COutput; class CScript; class CTxMemPool; class CWalletTx; class ReserveDestination; //! Default for -addresstype constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::LEGACY}; //! Default for -changetype constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO}; static constexpr uint64_t KNOWN_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA | WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_DESCRIPTORS; static constexpr uint64_t MUTABLE_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE; static const std::map<std::string, WalletFlags> WALLET_FLAG_MAP{ {"avoid_reuse", WALLET_FLAG_AVOID_REUSE}, {"blank", WALLET_FLAG_BLANK_WALLET}, {"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA}, {"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS}, {"descriptor_wallet", WALLET_FLAG_DESCRIPTORS}, }; extern const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS; /** * A wrapper to reserve an address from a wallet * * ReserveDestination is used to reserve an address. * It is currently only used inside of CreateTransaction. * * Instantiating a ReserveDestination does not reserve an address. To do so, * GetReservedDestination() needs to be called on the object. Once an address * has been reserved, call KeepDestination() on the ReserveDestination object to * make sure it is not returned. Call ReturnDestination() to return the address * so it can be re-used (for example, if the address was used in a new * transaction and that transaction was not completed and needed to be aborted). * * If an address is reserved and KeepDestination() is not called, then the * address will be returned when the ReserveDestination goes out of scope. */ class ReserveDestination { protected: //! The wallet to reserve from const CWallet *const pwallet; //! The ScriptPubKeyMan to reserve from. Based on type when //! GetReservedDestination is called ScriptPubKeyMan *m_spk_man{nullptr}; OutputType const type; //! The index of the address's key in the keypool int64_t nIndex{-1}; //! The destination CTxDestination address; //! Whether this is from the internal (change output) keypool bool fInternal{false}; public: //! Construct a ReserveDestination object. This does NOT reserve an address //! yet explicit ReserveDestination(CWallet *_pwallet, OutputType _type) : pwallet(_pwallet), type(_type) {} ReserveDestination(const ReserveDestination &) = delete; ReserveDestination &operator=(const ReserveDestination &) = delete; //! Destructor. If a key has been reserved and not KeepKey'ed, it will be //! returned to the keypool ~ReserveDestination() { ReturnDestination(); } //! Reserve an address bool GetReservedDestination(CTxDestination &pubkey, bool internal); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object //! goes out of scope void KeepDestination(); }; /** Address book data */ class CAddressBookData { private: bool m_change{true}; std::string m_label; public: std::string purpose; CAddressBookData() : purpose("unknown") {} typedef std::map<std::string, std::string> StringMap; StringMap destdata; bool IsChange() const { return m_change; } const std::string &GetLabel() const { return m_label; } void SetLabel(const std::string &label) { m_change = false; m_label = label; } }; struct CRecipient { CScript scriptPubKey; Amount nAmount; bool fSubtractFeeFromAmount; }; typedef std::map<std::string, std::string> mapValue_t; static inline void ReadOrderPos(int64_t &nOrderPos, mapValue_t &mapValue) { if (!mapValue.count("n")) { // TODO: calculate elsewhere nOrderPos = -1; return; } nOrderPos = atoi64(mapValue["n"].c_str()); } static inline void WriteOrderPos(const int64_t &nOrderPos, mapValue_t &mapValue) { if (nOrderPos == -1) { return; } mapValue["n"] = ToString(nOrderPos); } struct COutputEntry { CTxDestination destination; Amount amount; int vout; }; /** * Legacy class used for deserializing vtxPrev for backwards compatibility. * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. * These need to get deserialized for field alignment when deserializing * a CWalletTx, but the deserialized values are discarded. */ class CMerkleTx { public: template <typename Stream> void Unserialize(Stream &s) { CTransactionRef tx; BlockHash hashBlock; std::vector<uint256> vMerkleBranch; int nIndex = 0; s >> tx >> hashBlock >> vMerkleBranch >> nIndex; } }; // Get the marginal bytes of spending the specified output int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *pwallet, bool use_max_sig = false); /** * A transaction with a bunch of additional info that only the owner cares * about. It includes any unrecorded transactions needed to link it back to the * block chain. */ class CWalletTx { private: const CWallet *pwallet; /** * Constant used in hashBlock to indicate tx has been abandoned, only used * at serialization/deserialization to avoid ambiguity with conflicted. */ static const BlockHash ABANDON_HASH; public: /** * Key/value map with information about the transaction. * * The following keys can be read and written through the map and are * serialized in the wallet database: * * "comment", "to" - comment strings provided to sendtoaddress, * and sendmany wallet RPCs * "replaces_txid" - txid (as HexStr) of transaction replaced by * bumpfee on transaction created by bumpfee * "replaced_by_txid" - txid (as HexStr) of transaction created by * bumpfee on transaction replaced by bumpfee * "from", "message" - obsolete fields that could be set in UI prior to * 2011 (removed in commit 4d9b223) * * The following keys are serialized in the wallet database, but shouldn't * be read or written through the map (they will be temporarily added and * removed from the map during serialization): * * "fromaccount" - serialized strFromAccount value * "n" - serialized nOrderPos value * "timesmart" - serialized nTimeSmart value * "spent" - serialized vfSpent value that existed prior to * 2014 (removed in commit 93a18a3) */ mapValue_t mapValue; std::vector<std::pair<std::string, std::string>> vOrderForm; unsigned int fTimeReceivedIsTxTime; //! time received by this node unsigned int nTimeReceived; /** * Stable timestamp that never changes, and reflects the order a transaction * was added to the wallet. Timestamp is based on the block time for a * transaction added as part of a block, or else the time when the * transaction was received if it wasn't part of a block, with the timestamp * adjusted in both cases so timestamp order matches the order transactions * were added to the wallet. More details can be found in * CWallet::ComputeTimeSmart(). */ unsigned int nTimeSmart; /** * From me flag is set to 1 for transactions that were created by the wallet * on this bitcoin node, and set to 0 for transactions that were created * externally and came in through the network or sendrawtransaction RPC. */ bool fFromMe; //! position in ordered transaction list int64_t nOrderPos; std::multimap<int64_t, CWalletTx *>::const_iterator m_it_wtxOrdered; // memory only enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; Amount GetCachableAmount(AmountType type, const isminefilter &filter, bool recalculate = false) const; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; /** * This flag is true if all m_amounts caches are empty. This is particularly * useful in places where MarkDirty is conditionally called and the * condition can be expensive and thus can be skipped if the flag is true. * See MarkDestinationsDirty. */ mutable bool m_is_cache_empty{true}; mutable bool fChangeCached; mutable bool fInMempool; mutable Amount nChangeCached; CWalletTx(const CWallet *pwalletIn, CTransactionRef arg) : tx(std::move(arg)) { Init(pwalletIn); } void Init(const CWallet *pwalletIn) { pwallet = pwalletIn; mapValue.clear(); vOrderForm.clear(); fTimeReceivedIsTxTime = false; nTimeReceived = 0; nTimeSmart = 0; fFromMe = false; fChangeCached = false; fInMempool = false; nChangeCached = Amount::zero(); nOrderPos = -1; m_confirm = Confirmation{}; } CTransactionRef tx; /** * New transactions start as UNCONFIRMED. At BlockConnected, * they will transition to CONFIRMED. In case of reorg, at * BlockDisconnected, they roll back to UNCONFIRMED. If we detect a * conflicting transaction at block connection, we update conflicted tx and * its dependencies as CONFLICTED. If tx isn't confirmed and outside of * mempool, the user may switch it to ABANDONED by using the * abandontransaction call. This last status may be override by a CONFLICTED * or CONFIRMED transition. */ enum Status { UNCONFIRMED, CONFIRMED, CONFLICTED, ABANDONED }; /** * Confirmation includes tx status and a triplet of {block height/block * hash/tx index in block} at which tx has been confirmed. All three are set * to 0 if tx is unconfirmed or abandoned. Meaning of these fields changes * with CONFLICTED state where they instead point to block hash and block * height of the deepest conflicting tx. */ struct Confirmation { Status status; int block_height; BlockHash hashBlock; int nIndex; Confirmation(Status s = UNCONFIRMED, int b = 0, BlockHash h = BlockHash(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {} }; Confirmation m_confirm; template <typename Stream> void Serialize(Stream &s) const { mapValue_t mapValueCopy = mapValue; mapValueCopy["fromaccount"] = ""; WriteOrderPos(nOrderPos, mapValueCopy); if (nTimeSmart) { mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); } //! Used to be vMerkleBranch std::vector<char> dummy_vector1; //! Used to be vtxPrev std::vector<char> dummy_vector2; //! Used to be fSpent bool dummy_bool = false; uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; } template <typename Stream> void Unserialize(Stream &s) { Init(nullptr); //! Used to be vMerkleBranch std::vector<uint256> dummy_vector1; //! Used to be vtxPrev std::vector<CMerkleTx> dummy_vector2; //! Used to be fSpent bool dummy_bool; int serializedIndex; s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; /* * At serialization/deserialization, an nIndex == -1 means that * hashBlock refers to the earliest block in the chain we know this or * any in-wallet ancestor conflicts with. If nIndex == -1 and hashBlock * is ABANDON_HASH, it means transaction is abandoned. In same context, * an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) * or unconfirmed one. Older clients interpret nIndex == -1 as * unconfirmed for backward compatibility (pre-commit 9ac63d6). */ if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { setAbandoned(); } else if (serializedIndex == -1) { setConflicted(); } else if (!m_confirm.hashBlock.IsNull()) { m_confirm.nIndex = serializedIndex; setConfirmed(); } ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; mapValue.erase("fromaccount"); mapValue.erase("spent"); mapValue.erase("n"); mapValue.erase("timesmart"); } void SetTx(CTransactionRef arg) { tx = std::move(arg); } //! make sure balances are recalculated void MarkDirty() { m_amounts[DEBIT].Reset(); m_amounts[CREDIT].Reset(); m_amounts[IMMATURE_CREDIT].Reset(); m_amounts[AVAILABLE_CREDIT].Reset(); fChangeCached = false; m_is_cache_empty = true; } void BindWallet(CWallet *pwalletIn) { pwallet = pwalletIn; MarkDirty(); } //! filter decides which addresses will count towards the debit Amount GetDebit(const isminefilter &filter) const; Amount GetCredit(const isminefilter &filter) const; Amount GetImmatureCredit(bool fUseCache = true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type // CWallet. Amount GetAvailableCredit(bool fUseCache = true, const isminefilter &filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; Amount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; Amount GetChange() const; // Get the marginal bytes if spending the specified output from this // transaction int GetSpendSize(unsigned int out, bool use_max_sig = false) const { return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); } void GetAmounts(std::list<COutputEntry> &listReceived, std::list<COutputEntry> &listSent, Amount &nFee, const isminefilter &filter) const; bool IsFromMe(const isminefilter &filter) const { return GetDebit(filter) > Amount::zero(); } // True if only scriptSigs are different bool IsEquivalentTo(const CWalletTx &tx) const; bool InMempool() const; bool IsTrusted() const; bool IsTrusted(std::set<TxId> &trusted_parents) const; int64_t GetTxTime() const; // Pass this transaction to node for mempool insertion and relay to peers if // flag set to true bool SubmitMemoryPoolAndRelay(std::string &err_string, bool relay); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to // resolve the issue of member access into incomplete type CWallet. Note // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" // in place. std::set<TxId> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; /** * Return depth of transaction in blockchain: * <0 : conflicts with a transaction this deep in the blockchain * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to // resolve the issue of member access into incomplete type CWallet. Note // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" // in place. int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; bool IsInMainChain() const { return GetDepthInMainChain() > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ int GetBlocksToMaturity() const; bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } void setAbandoned() { m_confirm.status = CWalletTx::ABANDONED; m_confirm.hashBlock = BlockHash(); m_confirm.block_height = 0; m_confirm.nIndex = 0; } bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; } void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } TxId GetId() const { return tx->GetId(); } bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsImmatureCoinBase() const; }; class COutput { public: const CWalletTx *tx; int i; int nDepth; /** * Pre-computed estimated size of this output as a fully-signed input in a * transaction. Can be -1 if it could not be calculated. */ int nInputBytes; /** Whether we have the private keys to spend this output */ bool fSpendable; /** Whether we know how to spend this output, ignoring the lack of keys */ bool fSolvable; /** * Whether to use the maximum sized, 72 byte signature when calculating the * size of the input spend. This should only be set when watch-only outputs * are allowed. */ bool use_max_sig; /** * Whether this output is considered safe to spend. Unconfirmed transactions * from outside keys are considered unsafe and will not be used to fund new * spending transactions. */ bool fSafe; COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) { tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; // If known and signable by the given wallet, compute nInputBytes // Failure will keep this value -1 if (fSpendable && tx) { nInputBytes = tx->GetSpendSize(i, use_max_sig); } } std::string ToString() const; inline CInputCoin GetInputCoin() const { return CInputCoin(tx->tx, i, nInputBytes); } }; struct CoinSelectionParams { bool use_bnb = true; size_t change_output_size = 0; size_t change_spend_size = 0; CFeeRate effective_fee = CFeeRate(Amount::zero()); size_t tx_noinputs_size = 0; //! Indicate that we are subtracting the fee from outputs bool m_subtract_fee_outputs = false; CoinSelectionParams(bool use_bnb_, size_t change_output_size_, size_t change_spend_size_, CFeeRate effective_fee_, size_t tx_noinputs_size_) : use_bnb(use_bnb_), change_output_size(change_output_size_), change_spend_size(change_spend_size_), effective_fee(effective_fee_), tx_noinputs_size(tx_noinputs_size_) {} CoinSelectionParams() {} }; // forward declarations for ScanForWalletTransactions/RescanFromTime class WalletRescanReserver; /** * A CWallet maintains a set of transactions and balances, and provides the * ability to create new transactions. */ class CWallet final : public WalletStorage, public interfaces::Chain::Notifications { private: CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet); bool Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys = false); std::atomic<bool> fAbortRescan{false}; // controlled by WalletRescanReserver std::atomic<bool> fScanningWallet{false}; std::atomic<int64_t> m_scanning_start{0}; std::atomic<double> m_scanning_progress{0}; friend class WalletRescanReserver; //! the current wallet version: clients below this version are not able to //! load the wallet int nWalletVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; //! the maximum wallet format version: memory-only variable that specifies //! to what version this wallet may be upgraded int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; int64_t nNextResend = 0; bool fBroadcastTransactions = false; // Local time that the tip block was received. Used to schedule wallet // rebroadcasts. std::atomic<int64_t> m_best_block_time{0}; /** * Used to keep track of spent outpoints, and detect and report conflicts * (double-spends or mutated transactions where the mutant gets mined). */ typedef std::multimap<COutPoint, TxId> TxSpends; TxSpends mapTxSpends GUARDED_BY(cs_wallet); void AddToSpends(const COutPoint &outpoint, const TxId &wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void AddToSpends(const TxId &wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. pIndex and posInBlock * should be set when the transaction was known to be included in a * block. When *pIndex == nullptr, then wallet state is not updated in * AddToWallet, but notifications happen and cached balances are marked * dirty. * * If fUpdate is true, existing transactions will be updated. * TODO: One exception to this is that the abandoned state is cleared under * the assumption that any further notification of a transaction that was * considered abandoned is an indication that it is not safe to be * considered abandoned. Abandoned state should probably be more carefully * tracked via different posInBlock signals or by checking mempool presence * when necessary. */ bool AddToWalletIfInvolvingMe(const CTransactionRef &tx, CWalletTx::Confirmation confirm, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Mark a transaction (and its in-wallet descendants) as conflicting with a * particular block. */ void MarkConflicted(const BlockHash &hashBlock, int conflicting_height, const TxId &txid); /** * Mark a transaction's inputs dirty, thus forcing the outputs to be * recomputed */ void MarkInputsDirty(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Used by * TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with non-zero block_hash and posInBlock if this is for a * transaction that is included in a block. */ void SyncTransaction(const CTransactionRef &tx, CWalletTx::Confirmation confirm, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::atomic<uint64_t> m_wallet_flags{0}; bool SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address, const std::string &strName, const std::string &strPurpose); //! Unsets a wallet flag and saves it to disk void UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag); //! Unset the blank wallet flag and saves it to disk void UnsetBlankWalletFlag(WalletBatch &batch) override; /** Interface for accessing chain state. */ interfaces::Chain *m_chain; /** * Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; /** Internal database handle. */ std::unique_ptr<WalletDatabase> database; /** * The following is used to keep track of how far behind the wallet is * from the chain sync, and to allow clients to block on us being caught up. * * Processed hash is a pointer on node's tip and doesn't imply that the * wallet has scanned sequentially all blocks up to this one. */ BlockHash m_last_block_processed GUARDED_BY(cs_wallet); /* Height of last block processed is used by wallet to know depth of * transactions without relying on Chain interface beyond asynchronous * updates. For safety, we initialize it to -1. Height is a pointer on * node's tip and doesn't imply that the wallet has scanned sequentially all * blocks up to this one. */ int m_last_block_processed_height GUARDED_BY(cs_wallet) = -1; bool CreateTransactionInternal(const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, const CCoinControl &coin_control, bool sign); std::map<OutputType, ScriptPubKeyMan *> m_external_spk_managers; std::map<OutputType, ScriptPubKeyMan *> m_internal_spk_managers; // Indexed by a unique identifier produced by each ScriptPubKeyMan using // ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal // structure std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers; public: /* * Main wallet lock. * This lock protects all the fields added by CWallet. */ mutable RecursiveMutex cs_wallet; /** * Get database handle used by this wallet. Ideally this function would not * be necessary. */ WalletDatabase &GetDBHandle() { return *database; } WalletDatabase &GetDatabase() override { return *database; } /** * Select a set of coins such that nValueRet >= nTargetValue and at least * all coins from coinControl are selected; Never select unconfirmed coins * if they are not ours. */ bool SelectCoins(const std::vector<COutput> &vAvailableCoins, const Amount nTargetValue, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CCoinControl &coin_control, CoinSelectionParams &coin_selection_params, bool &bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const WalletLocation &GetLocation() const { return m_location; } /** * Get a name for this wallet for logging/debugging purposes. */ const std::string &GetName() const { return m_location.GetName(); } typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ CWallet(interfaces::Chain *chain, const WalletLocation &location, std::unique_ptr<WalletDatabase> _database) : m_chain(chain), m_location(location), database(std::move(_database)) { } ~CWallet() { // Should not have slots connected at this point. assert(NotifyUnload.empty()); } /* Returns the chain params used by this wallet. */ const CChainParams &GetChainParams() const override; bool IsCrypted() const; bool IsLocked() const override; bool Lock(); /** Interface to assert chain access */ bool HaveChain() const { return m_chain ? true : false; } std::map<TxId, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx *> TxItems; TxItems wtxOrdered; int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; uint64_t nAccountingEntryNumber = 0; std::map<CTxDestination, CAddressBookData> m_address_book GUARDED_BY(cs_wallet); const CAddressBookData * FindAddressBookEntry(const CTxDestination &, bool allow_change = false) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); /** Registered interfaces::Chain::Notifications handler. */ std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; /** Interface for accessing chain state. */ interfaces::Chain &chain() const { assert(m_chain); return *m_chain; } const CWalletTx *GetWalletTx(const TxId &txid) const; //! check whether we are allowed to upgrade (or already support) to the //! named feature bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * populate vCoins with vector of available COutputs. */ void AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, const Amount nMinimumAmount = SATOSHI, const Amount nMaximumAmount = MAX_MONEY, const Amount nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change * output address. */ std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. */ const CTxOut &FindNonChangeParentOutput(const CTransaction &tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Shuffle and select coins until nTargetValue is reached while avoiding * small change; This method is stochastic for some inputs and upon * completion the coin set and corresponding actual target value is * assembled. */ bool SelectCoinsMinConf(const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CoinSelectionParams &coin_selection_params, bool &bnb_used) const; bool IsSpent(const COutPoint &outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Whether this or any UTXO with the same CTxDestination has been spent. bool IsSpentKey(const TxId &txid, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetSpentKeyState(WalletBatch &batch, const TxId &txid, unsigned int n, bool used, std::set<CTxDestination> &tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput> &outputs, bool single_coin, const size_t max_ancestors) const; bool IsLockedCoin(const COutPoint &outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LockCoin(const COutPoint &output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void UnlockCoin(const COutPoint &output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ListLockedCoins(std::vector<COutPoint> &vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* * Rescan abort properties */ void AbortRescan() { fAbortRescan = true; } bool IsAbortingRescan() const { return fAbortRescan; } bool IsScanning() const { return fScanningWallet; } int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } double ScanningProgress() const { return fScanningWallet ? double(m_scanning_progress) : 0; } //! Upgrade stored CKeyMetadata objects to store key origin info as //! KeyOriginInfo void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } /** * Adds a destination data tuple to the store, and saves it to disk * When adding new fields, take care to consider how DelAddressBook should * handle it! */ bool AddDestData(WalletBatch &batch, const CTxDestination &dest, const std::string &key, const std::string &value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Erases a destination data tuple in the store and on disk bool EraseDestData(WalletBatch &batch, const CTxDestination &dest, const std::string &key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a destination data tuple to the store, without saving it to disk void LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Look up a destination data tuple in the store, return true if found //! false otherwise bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Get all destination values matching a prefix. std::vector<std::string> GetDestValues(const std::string &prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Holds a timestamp at which point the wallet is scheduled (externally) to //! be relocked. Caller must arrange for actual relocking to occur via //! Lock(). int64_t nRelockTime GUARDED_BY(cs_wallet){0}; // Used to prevent concurrent calls to walletpassphrase RPC. Mutex m_unlock_mutex; bool Unlock(const SecureString &strWalletPassphrase, bool accept_no_keys = false); bool ChangeWalletPassphrase(const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase); bool EncryptWallet(const SecureString &strWalletPassphrase); void GetKeyBirthTimes(std::map<CKeyID, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx &wtx) const; /** * Increment the next transaction order id * @return next transaction order id */ int64_t IncOrderPosNext(WalletBatch *batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); DBErrors ReorderTransactions(); void MarkDirty(); - bool AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose = true); + + //! Callback for updating transaction metadata in mapWallet. + //! + //! @param wtx - reference to mapWallet transaction to update + //! @param new_tx - true if wtx is newly inserted, false if it previously + //! existed + //! + //! @return true if wtx is changed and needs to be saved to disk, otherwise + //! false + using UpdateWalletTxFn = std::function<bool(CWalletTx &wtx, bool new_tx)>; + + CWalletTx *AddToWallet(CTransactionRef tx, + const CWalletTx::Confirmation &confirm, + const UpdateWalletTxFn &update_wtx = nullptr, + bool fFlushOnClose = true); void LoadToWallet(CWalletTx &wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void transactionAddedToMempool(const CTransactionRef &tx) override; void blockConnected(const CBlock &block, int height) override; void blockDisconnected(const CBlock &block, int height) override; void updatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver &reserver, bool update); struct ScanResult { enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS; //! Hash and height of most recent block that was successfully scanned. //! Unset if no blocks were scanned due to read errors or the chain //! being empty. BlockHash last_scanned_block; std::optional<int> last_scanned_height; //! Hash of the most recent block that could not be scanned due to //! read errors or pruning. Will be set if status is FAILURE, unset if //! status is SUCCESS, and may or may not be set if status is //! USER_ABORT. BlockHash last_failed_block; }; ScanResult ScanForWalletTransactions(const BlockHash &start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver &reserver, bool fUpdate); void transactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); struct Balance { //! Trusted, at depth=GetBalance.min_depth or more Amount m_mine_trusted{Amount::zero()}; //! Untrusted, but in mempool (pending) Amount m_mine_untrusted_pending{Amount::zero()}; //! Immature coinbases in the main chain Amount m_mine_immature{Amount::zero()}; Amount m_watchonly_trusted{Amount::zero()}; Amount m_watchonly_untrusted_pending{Amount::zero()}; Amount m_watchonly_immature{Amount::zero()}; }; Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const; Amount GetAvailableBalance(const CCoinControl *coinControl = nullptr) const; OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient> &vecSend); /** * Insert additional inputs into the transaction by calling * CreateTransaction(); */ bool FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, bool lockUnspents, const std::set<int> &setSubtractFeeFromOutputs, CCoinControl coinControl); // Fetch the inputs and sign with SIGHASH_ALL. bool SignTransaction(CMutableTransaction &tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Sign the tx given the input coins and sighash. bool SignTransaction(CMutableTransaction &tx, const std::map<COutPoint, Coin> &coins, SigHashType sighash, std::map<int, std::string> &input_errors) const; SigningResult SignMessage(const std::string &message, const PKHash &pkhash, std::string &str_sig) const; /** * Fills out a PSBT with information from the wallet. Fills in UTXOs if we * have them. Tries to sign if sign=true. Sets `complete` if the PSBT is now * complete (i.e. has all required signatures or signature-parts, and is * ready to finalize.) Sets `error` and returns false if something goes * wrong. * * @param[in] psbtx PartiallySignedTransaction to fill in * @param[out] complete indicates whether the PSBT is now complete * @param[in] sighash_type the sighash type to use when signing (if PSBT * does not specify) * @param[in] sign whether to sign or not * @param[in] bip32derivs whether to fill in bip32 derivation information * if available return error */ TransactionError FillPSBT(PartiallySignedTransaction &psbtx, bool &complete, SigHashType sighash_type = SigHashType().withForkId(), bool sign = true, bool bip32derivs = true) const; /** * Create a new transaction paying the recipients with a set of coins * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random * position */ bool CreateTransaction(const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, bilingual_str &error, const CCoinControl &coin_control, bool sign = true); /** * Submit the transaction to the node's mempool and then relay to peers. * Should be called after CreateTransaction unless you want to abort * broadcasting the transaction. * * @param[in] tx The transaction to be broadcast. * @param[in] mapValue key-values to be set on the transaction. * @param[in] orderForm BIP 70 / BIP 21 order form details to be set on the * transaction. */ void CommitTransaction( CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { std::vector<CTxOut> v_txouts(txouts.size()); std::copy(txouts.begin(), txouts.end(), v_txouts.begin()); return DummySignTx(txNew, v_txouts, use_max_sig); } bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const; bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPubKeys( const std::vector<CKeyID> &ordered_pubkeys, const std::map<CKeyID, CPubKey> &pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportScriptPubKeys(const std::string &label, const std::set<CScript> &script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE}; //! will be false if -fallbackfee=0 bool m_allow_fallback_fee{true}; // Override with -mintxfee CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE_PER_KB}; /** * If fee estimation does not have enough data to provide estimates, use * this fee instead. Has no effect if not using fee estimation Override with * -fallbackfee */ CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE}; //! note: this is absolute fee, not fee rate Amount m_max_aps_fee{DEFAULT_MAX_AVOIDPARTIALSPEND_FEE}; OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE}; OutputType m_default_change_type{DEFAULT_CHANGE_TYPE}; /** * Absolute maximum transaction fee (in satoshis) used by default for the * wallet. */ Amount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; size_t KeypoolCountExternalKeys() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); int64_t GetOldestKeyPoolTime() const; std::set<std::set<CTxDestination>> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::map<CTxDestination, Amount> GetAddressBalances() const; std::set<CTxDestination> GetLabelAddresses(const std::string &label) const; /** * Marks all outputs in each one of the destinations dirty, so their cache * is reset and does not return outdated information. */ void MarkDestinationsDirty(const std::set<CTxDestination> &destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool GetNewDestination(const OutputType type, const std::string label, CTxDestination &dest, std::string &error); bool GetNewChangeDestination(const OutputType type, CTxDestination &dest, std::string &error); isminetype IsMine(const CTxDestination &dest) const; isminetype IsMine(const CScript &script) const; isminetype IsMine(const CTxIn &txin) const; /** * Returns amount of debit if the input matches the filter, otherwise * returns 0 */ Amount GetDebit(const CTxIn &txin, const isminefilter &filter) const; isminetype IsMine(const CTxOut &txout) const; Amount GetCredit(const CTxOut &txout, const isminefilter &filter) const; bool IsChange(const CTxOut &txout) const; bool IsChange(const CScript &script) const; Amount GetChange(const CTxOut &txout) const; bool IsMine(const CTransaction &tx) const; /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction &tx) const; Amount GetDebit(const CTransaction &tx, const isminefilter &filter) const; /** Returns whether all of the inputs match the filter */ bool IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const; Amount GetCredit(const CTransaction &tx, const isminefilter &filter) const; Amount GetChange(const CTransaction &tx) const; void chainStateFlushed(const CBlockLocator &loc) override; DBErrors LoadWallet(bool &fFirstRunRet); DBErrors ZapWalletTx(std::vector<CWalletTx> &vWtx); DBErrors ZapSelectTx(std::vector<TxId> &txIdsIn, std::vector<TxId> &txIdsOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &purpose); bool DelAddressBook(const CTxDestination &address); unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! signify that a particular wallet feature is now used. this may change //! nWalletVersion and nWalletMaxVersion if those are lower void SetMinVersion(enum WalletFeature, WalletBatch *batch_in = nullptr, bool fExplicit = false) override; //! change which version we're allowed to upgrade to (note that this does //! not immediately imply upgrading to that format) bool SetMaxVersion(int nVersion); //! get the current wallet format (the oldest client version guaranteed to //! understand this wallet) int GetVersion() const { LOCK(cs_wallet); return nWalletVersion; } //! Get wallet transactions that conflict with given transaction (spend same //! outputs) std::set<TxId> GetConflicts(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Check if a given transaction has any of its outputs spent by another //! transaction in the wallet bool HasWalletSpend(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Flush wallet (bitdb flush) void Flush(bool shutdown = false); /** Wallet is about to be unloaded */ boost::signals2::signal<void()> NotifyUnload; /** * Address book entry changed. * @note called with lock cs_wallet held. */ boost::signals2::signal<void(CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status)> NotifyAddressBookChanged; /** * Wallet transaction added, removed or updated. * @note called with lock cs_wallet held. */ boost::signals2::signal<void(CWallet *wallet, const TxId &txid, ChangeType status)> NotifyTransactionChanged; /** Show progress e.g. for rescan */ boost::signals2::signal<void(const std::string &title, int nProgress)> ShowProgress; /** Watch-only address added */ boost::signals2::signal<void(bool fHaveWatchOnly)> NotifyWatchonlyChanged; /** Keypool has new keys */ boost::signals2::signal<void()> NotifyCanGetAddressesChanged; /** * Wallet status (encrypted, locked) changed. * Note: Called without locks held. */ boost::signals2::signal<void(CWallet *wallet)> NotifyStatusChanged; /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } /** Return whether transaction can be abandoned */ bool TransactionCanBeAbandoned(const TxId &txid) const; /** * Mark a transaction (and it in-wallet descendants) as abandoned so its * inputs may be respent. */ bool AbandonTransaction(const TxId &txid); //! Verify wallet naming and perform salvage on the wallet if required static bool Verify(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error_string, std::vector<bilingual_str> &warnings); /** * Initializes the wallet, returns a new CWallet instance or a null pointer * in case of an error. */ static std::shared_ptr<CWallet> CreateWalletFromFile(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bilingual_str &error, std::vector<bilingual_str> &warnings, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup * Gives the wallet a chance to register repetitive tasks and complete * post-init tasks */ void postInitProcess(); bool BackupWallet(const std::string &strDest) const; /* Returns true if HD is enabled */ bool IsHDEnabled() const; /** * Returns true if the wallet can give out new addresses. This means it has * keys in the keypool or can generate new keys. */ bool CanGetAddresses(bool internal = false) const; /** * Blocks until the wallet state is up-to-date to /at least/ the current * chain at the time this function is entered. * Obviously holding cs_main/cs_wallet when going into this call may cause * deadlock */ void BlockUntilSyncedToCurrentChain() const LOCKS_EXCLUDED(cs_main, cs_wallet); /** * Set a single wallet flag. */ void SetWalletFlag(uint64_t flags); /** * Unsets a single wallet flag. */ void UnsetWalletFlag(uint64_t flag); /** * Check if a certain wallet flag is set. */ bool IsWalletFlagSet(uint64_t flag) const override; /** * Overwrite all flags by the given uint64_t. * Returns false if unknown, non-tolerable flags are present. */ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly); /** Determine if we are a legacy wallet */ bool IsLegacy() const; /** * Returns a bracketed wallet name for displaying in logs, will return * [default wallet] if the wallet has no name. */ const std::string GetDisplayName() const override { std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName(); return strprintf("[%s]", wallet_name); }; /** * Prepends the wallet name in logging output to ease debugging in * multi-wallet use cases. */ template <typename... Params> void WalletLogPrintf(std::string fmt, Params... parameters) const { LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; template <typename... Params> void WalletLogPrintfToBeContinued(std::string fmt, Params... parameters) const { LogPrintfToBeContinued(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; /** Upgrade the wallet */ bool UpgradeWallet(int version, bilingual_str &error, std::vector<bilingual_str> &warnings); //! Returns all unique ScriptPubKeyMans in m_internal_spk_managers and //! m_external_spk_managers std::set<ScriptPubKeyMan *> GetActiveScriptPubKeyMans() const; //! Returns all unique ScriptPubKeyMans std::set<ScriptPubKeyMan *> GetAllScriptPubKeyMans() const; //! Get the ScriptPubKeyMan for the given OutputType and internal/external //! chain. ScriptPubKeyMan *GetScriptPubKeyMan(const OutputType &type, bool internal) const; //! Get the ScriptPubKeyMan for a script ScriptPubKeyMan *GetScriptPubKeyMan(const CScript &script) const; //! Get the ScriptPubKeyMan by id ScriptPubKeyMan *GetScriptPubKeyMan(const uint256 &id) const; //! Get all of the ScriptPubKeyMans for a script given additional //! information in sigdata (populated by e.g. a psbt) std::set<ScriptPubKeyMan *> GetScriptPubKeyMans(const CScript &script, SignatureData &sigdata) const; //! Get the SigningProvider for a script std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript &script) const; std::unique_ptr<SigningProvider> GetSolvingProvider(const CScript &script, SignatureData &sigdata) const; //! Get the LegacyScriptPubKeyMan which is used for all types, internal, and //! external. LegacyScriptPubKeyMan *GetLegacyScriptPubKeyMan() const; LegacyScriptPubKeyMan *GetOrCreateLegacyScriptPubKeyMan(); //! Make a LegacyScriptPubKeyMan and set it for all types, internal, and //! external. void SetupLegacyScriptPubKeyMan(); const CKeyingMaterial &GetEncryptionKey() const override; bool HasEncryptionKeys() const override; /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); assert(m_last_block_processed_height >= 0); return m_last_block_processed_height; }; BlockHash GetLastBlockHash() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); assert(m_last_block_processed_height >= 0); return m_last_block_processed; } /** Set last block processed height, currently only use in unit test */ void SetLastBlockProcessed(int block_height, BlockHash block_hash) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); m_last_block_processed_height = block_height; m_last_block_processed = block_hash; }; //! Connect the signals from ScriptPubKeyMans to the signals in CWallet void ConnectScriptPubKeyManNotifiers(); //! Instantiate a descriptor ScriptPubKeyMan from the WalletDescriptor and //! load it void LoadDescriptorScriptPubKeyMan(uint256 id, WalletDescriptor &desc); //! Sets the active ScriptPubKeyMan for the specified type and internal //! @param[in] id The unique id for the ScriptPubKeyMan //! @param[in] type The OutputType this ScriptPubKeyMan provides addresses //! for //! @param[in] internal Whether this ScriptPubKeyMan provides change //! addresses //! @param[in] memonly Whether to record this update to the database. Set to //! true for wallet loading, normally false when actually //! updating the wallet. void SetActiveScriptPubKeyMan(uint256 id, OutputType type, bool internal, bool memonly = false); //! Create new DescriptorScriptPubKeyMans and add them to the wallet void SetupDescriptorScriptPubKeyMans(); //! Return the DescriptorScriptPubKeyMan for a WalletDescriptor if it is //! already in the wallet DescriptorScriptPubKeyMan * GetDescriptorScriptPubKeyMan(const WalletDescriptor &desc) const; //! Add a descriptor to the wallet, return a ScriptPubKeyMan & associated //! output type ScriptPubKeyMan * AddWalletDescriptor(WalletDescriptor &desc, const FlatSigningProvider &signing_provider, const std::string &label); }; /** * Called periodically by the schedule thread. Prompts individual wallets to * resend their transactions. Actual rebroadcast schedule is managed by the * wallets themselves. */ void MaybeResendWalletTxs(); /** RAII object to check and reserve a wallet rescan */ class WalletRescanReserver { private: CWallet &m_wallet; bool m_could_reserve; public: explicit WalletRescanReserver(CWallet &w) : m_wallet(w), m_could_reserve(false) {} bool reserve() { assert(!m_could_reserve); if (m_wallet.fScanningWallet.exchange(true)) { return false; } m_wallet.m_scanning_start = GetTimeMillis(); m_wallet.m_scanning_progress = 0; m_could_reserve = true; return true; } bool isReserved() const { return (m_could_reserve && m_wallet.fScanningWallet); } ~WalletRescanReserver() { if (m_could_reserve) { m_wallet.fScanningWallet = false; } } }; // Calculate the size of the transaction assuming all signatures are max size // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut> &txouts, bool use_max_sig = false); #endif // BITCOIN_WALLET_WALLET_H