diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/coincontrol.cpp | 2 | ||||
-rw-r--r-- | src/wallet/coincontrol.h | 6 | ||||
-rw-r--r-- | src/wallet/coinselection.cpp | 2 | ||||
-rw-r--r-- | src/wallet/crypter.h | 2 | ||||
-rw-r--r-- | src/wallet/feebumper.cpp | 17 | ||||
-rw-r--r-- | src/wallet/init.cpp | 4 | ||||
-rw-r--r-- | src/wallet/ismine.h | 2 | ||||
-rw-r--r-- | src/wallet/psbtwallet.cpp | 27 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 261 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.cpp | 102 | ||||
-rw-r--r-- | src/wallet/scriptpubkeyman.h | 41 | ||||
-rw-r--r-- | src/wallet/test/coinselector_tests.cpp | 52 | ||||
-rw-r--r-- | src/wallet/test/init_test_fixture.cpp | 2 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 228 | ||||
-rw-r--r-- | src/wallet/wallet.h | 58 | ||||
-rw-r--r-- | src/wallet/walletdb.h | 2 | ||||
-rw-r--r-- | src/wallet/wallettool.h | 2 | ||||
-rw-r--r-- | src/wallet/walletutil.h | 2 |
19 files changed, 508 insertions, 306 deletions
diff --git a/src/wallet/coincontrol.cpp b/src/wallet/coincontrol.cpp index 14513bc9e9..c83e598825 100644 --- a/src/wallet/coincontrol.cpp +++ b/src/wallet/coincontrol.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018 The Bitcoin Core developers +// Copyright (c) 2018-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. diff --git a/src/wallet/coincontrol.h b/src/wallet/coincontrol.h index fca4b75c45..2893d0ab3d 100644 --- a/src/wallet/coincontrol.h +++ b/src/wallet/coincontrol.h @@ -6,14 +6,18 @@ #define BITCOIN_WALLET_COINCONTROL_H #include <optional.h> +#include <outputtype.h> #include <policy/feerate.h> #include <policy/fees.h> #include <primitives/transaction.h> -#include <wallet/wallet.h> +#include <script/standard.h> const int DEFAULT_MIN_DEPTH = 0; const int DEFAULT_MAX_DEPTH = 9999999; +//! Default for -avoidpartialspends +static constexpr bool DEFAULT_AVOIDPARTIALSPENDS = false; + /** Coin Control Features. */ class CCoinControl { diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp index 870e235964..5bbb2c0ad0 100644 --- a/src/wallet/coinselection.cpp +++ b/src/wallet/coinselection.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018 The Bitcoin Core developers +// Copyright (c) 2017-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. diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h index 4367a5047f..f59c63260e 100644 --- a/src/wallet/crypter.h +++ b/src/wallet/crypter.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009-2018 The Bitcoin Core developers +// 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. diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp index 8f0b495ac4..b93b9ef1bc 100644 --- a/src/wallet/feebumper.cpp +++ b/src/wallet/feebumper.cpp @@ -47,7 +47,8 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet // check that original tx consists entirely of our inputs // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) - if (!wallet.IsAllFromMe(*wtx.tx, ISMINE_SPENDABLE)) { + isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + if (!wallet.IsAllFromMe(*wtx.tx, filter)) { errors.push_back("Transaction contains inputs that don't belong to this wallet"); return feebumper::Result::WALLET_ERROR; } @@ -78,7 +79,8 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt CFeeRate incrementalRelayFee = std::max(wallet.chain().relayIncrementalFee(), CFeeRate(WALLET_INCREMENTAL_RELAY_FEE)); // Given old total fee and transaction size, calculate the old feeRate - CAmount old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + CAmount old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); CFeeRate nOldFeeRate(old_fee, txSize); // Min total fee is old fee + relay fee @@ -108,12 +110,11 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt return feebumper::Result::OK; } -static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, CCoinControl& coin_control, CAmount& old_fee) +static CFeeRate EstimateFeeRate(const CWallet& wallet, const CWalletTx& wtx, const CAmount old_fee, CCoinControl& coin_control) { // Get the fee rate of the original transaction. This is calculated from // the tx fee/vsize, so it may have been rounded down. Add 1 satoshi to the // result. - old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); CFeeRate feerate(old_fee, txSize); feerate += CFeeRate(1); @@ -196,7 +197,8 @@ Result CreateTotalBumpTransaction(const CWallet* wallet, const uint256& txid, co } // calculate the old fee and fee-rate - old_fee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); + isminefilter filter = wallet->GetLegacyScriptPubKeyMan() && wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); CFeeRate nOldFeeRate(old_fee, txSize); // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to // future proof against changes to network wide policy for incremental relay @@ -309,6 +311,9 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } } + isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); + if (coin_control.m_feerate) { // The user provided a feeRate argument. // We calculate this here to avoid compiler warning on the cs_wallet lock @@ -319,7 +324,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo } } else { // The user did not provide a feeRate argument - new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, new_coin_control, old_fee); + new_coin_control.m_feerate = EstimateFeeRate(wallet, wtx, old_fee, new_coin_control); } // Fill in required inputs we are double-spending(all of them) diff --git a/src/wallet/init.cpp b/src/wallet/init.cpp index a8b3df1f2e..dd0d2ffbd7 100644 --- a/src/wallet/init.cpp +++ b/src/wallet/init.cpp @@ -8,9 +8,11 @@ #include <net.h> #include <node/context.h> #include <outputtype.h> +#include <ui_interface.h> #include <util/moneystr.h> #include <util/system.h> #include <util/translation.h> +#include <wallet/coincontrol.h> #include <wallet/wallet.h> #include <walletinitinterface.h> @@ -58,7 +60,7 @@ void WalletInit::AddWalletOptions() const gArgs.AddArg("-upgradewallet", "Upgrade wallet to latest format on startup", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); gArgs.AddArg("-wallet=<path>", "Specify wallet database path. Can be specified multiple times to load multiple wallets. Path is interpreted relative to <walletdir> if it is not absolute, and will be created if it does not exist (as a directory containing a wallet.dat file and log files). For backwards compatibility this will also accept names of existing data files in <walletdir>.)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); gArgs.AddArg("-walletbroadcast", strprintf("Make the wallet broadcast transactions (default: %u)", DEFAULT_WALLETBROADCAST), ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); - gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); + gArgs.AddArg("-walletdir=<dir>", "Specify directory to hold wallets (default: <datadir>/wallets if it exists, otherwise <datadir>)", ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY, OptionsCategory::WALLET); #if HAVE_SYSTEM gArgs.AddArg("-walletnotify=<cmd>", "Execute command when a wallet transaction changes (%s in cmd is replaced by TxID)", ArgsManager::ALLOW_ANY, OptionsCategory::WALLET); #endif diff --git a/src/wallet/ismine.h b/src/wallet/ismine.h index 0bc6c90354..5cdd7dff80 100644 --- a/src/wallet/ismine.h +++ b/src/wallet/ismine.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// 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. diff --git a/src/wallet/psbtwallet.cpp b/src/wallet/psbtwallet.cpp index aa13cacca4..96c1ad8d3f 100644 --- a/src/wallet/psbtwallet.cpp +++ b/src/wallet/psbtwallet.cpp @@ -39,12 +39,35 @@ TransactionError FillPSBT(const CWallet* pwallet, PartiallySignedTransaction& ps return TransactionError::SIGHASH_MISMATCH; } - complete &= SignPSBTInput(HidingSigningProvider(pwallet->GetSigningProvider(), !sign, !bip32derivs), psbtx, i, sighash_type); + // Get the scriptPubKey to know which SigningProvider to use + CScript script; + if (!input.witness_utxo.IsNull()) { + script = input.witness_utxo.scriptPubKey; + } else if (input.non_witness_utxo) { + script = input.non_witness_utxo->vout[txin.prevout.n].scriptPubKey; + } else { + // There's no UTXO so we can just skip this now + complete = false; + continue; + } + SignatureData sigdata; + input.FillSignatureData(sigdata); + const SigningProvider* provider = pwallet->GetSigningProvider(script, sigdata); + if (!provider) { + complete = false; + continue; + } + + complete &= SignPSBTInput(HidingSigningProvider(provider, !sign, !bip32derivs), psbtx, i, sighash_type); } // Fill in the bip32 keypaths and redeemscripts for the outputs so that hardware wallets can identify change for (unsigned int i = 0; i < psbtx.tx->vout.size(); ++i) { - UpdatePSBTOutput(HidingSigningProvider(pwallet->GetSigningProvider(), true, !bip32derivs), psbtx, i); + const CTxOut& out = psbtx.tx->vout.at(i); + const SigningProvider* provider = pwallet->GetSigningProvider(out.scriptPubKey); + if (provider) { + UpdatePSBTOutput(HidingSigningProvider(provider, true, !bip32derivs), psbtx, i); + } } return TransactionError::OK; diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index d95481500d..633ac1b16d 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -1286,7 +1286,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) {"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 in seconds since epoch (Jan 1 1970 GMT),\n" + {"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" diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 00742aed7a..85793ef180 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -560,7 +560,11 @@ static UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } - const SigningProvider* provider = pwallet->GetSigningProvider(); + CScript script_pub_key = GetScriptForDestination(*pkhash); + const SigningProvider* provider = pwallet->GetSigningProvider(script_pub_key); + if (!provider) { + throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + } CKey key; CKeyID keyID(*pkhash); @@ -947,7 +951,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) } RPCHelpMan{"addmultisigaddress", - "\nAdd a nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" + "\nAdd an nrequired-to-sign multisignature address to the wallet. Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" "This functionality is only intended for use with non-watchonly addresses.\n" "See `importaddress` for watchonly p2sh address support.\n" @@ -994,7 +998,7 @@ static UniValue addmultisigaddress(const JSONRPCRequest& request) if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); } else { - pubkeys.push_back(AddrToPubKey(&spk_man, keys_or_addrs[i].get_str())); + pubkeys.push_back(AddrToPubKey(spk_man, keys_or_addrs[i].get_str())); } } @@ -1370,14 +1374,14 @@ static const std::string TransactionDescriptionString() " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" " \"blockheight\": n, (numeric) The block height containing the transaction.\n" " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" - " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" + " \"blocktime\": xxx, (numeric) The block time expressed in " + UNIX_EPOCH_TIME + ".\n" " \"txid\": \"transactionid\", (string) The transaction id.\n" " \"walletconflicts\": [ (array) Conflicting transaction ids.\n" " \"txid\", (string) The transaction id.\n" " ...\n" " ],\n" - " \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT).\n" + " \"time\": xxx, (numeric) The transaction time expressed in " + UNIX_EPOCH_TIME + ".\n" + " \"timereceived\": xxx, (numeric) The time received expressed in " + UNIX_EPOCH_TIME + ".\n" " \"comment\": \"...\", (string) If a comment is associated with the transaction, only present if not empty.\n" " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" " may be unknown for unconfirmed transactions not in the mempool\n"; @@ -2424,10 +2428,10 @@ static UniValue getwalletinfo(const JSONRPCRequest& request) " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. Identical to getbalances().mine.untrusted_pending\n" " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. Identical to getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number of transactions in the wallet\n" - " \"keypoololdest\": xxxxxx, (numeric) the timestamp (seconds since Unix epoch) of the oldest pre-generated key in the key pool\n" + " \"keypoololdest\": xxxxxx, (numeric) the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys are pre-generated (only counts external keys)\n" " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys are pre-generated for internal use (used for change outputs, only appears if the wallet is using this feature, otherwise external keys are used)\n" - " \"unlocked_until\": ttt, (numeric) the timestamp in seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is unlocked for transfers, or 0 if the wallet is locked\n" + " \"unlocked_until\": ttt, (numeric) the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the Hash160 of the HD seed (only present when HD is enabled)\n" " \"private_keys_enabled\": true|false (boolean) false if privatekeys are disabled for this wallet (enforced watch-only wallet)\n" @@ -2923,7 +2927,7 @@ static UniValue listunspent(const JSONRPCRequest& request) CTxDestination address; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); - bool reused = avoid_reuse && pwallet->IsUsedDestination(address); + bool reused = avoid_reuse && pwallet->IsUsedDestination(out.tx->GetHash(), out.i); if (destinations.size() && (!fValidAddress || !destinations.count(address))) continue; @@ -2940,34 +2944,36 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("label", i->second.name); } - const SigningProvider* provider = pwallet->GetSigningProvider(); - if (scriptPubKey.IsPayToScriptHash()) { - const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); - CScript redeemScript; - if (provider->GetCScript(hash, redeemScript)) { - entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); - // Now check if the redeemScript is actually a P2WSH script - CTxDestination witness_destination; - if (redeemScript.IsPayToWitnessScriptHash()) { - bool extracted = ExtractDestination(redeemScript, witness_destination); - CHECK_NONFATAL(extracted); - // Also return the witness script - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + if (scriptPubKey.IsPayToScriptHash()) { + const CScriptID& hash = CScriptID(boost::get<ScriptHash>(address)); + CScript redeemScript; + if (provider->GetCScript(hash, redeemScript)) { + entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); + // Now check if the redeemScript is actually a P2WSH script + CTxDestination witness_destination; + if (redeemScript.IsPayToWitnessScriptHash()) { + bool extracted = ExtractDestination(redeemScript, witness_destination); + CHECK_NONFATAL(extracted); + // Also return the witness script + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(witness_destination); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } - } - } else if (scriptPubKey.IsPayToWitnessScriptHash()) { - const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); - CScriptID id; - CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); - CScript witnessScript; - if (provider->GetCScript(id, witnessScript)) { - entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } else if (scriptPubKey.IsPayToWitnessScriptHash()) { + const WitnessV0ScriptHash& whash = boost::get<WitnessV0ScriptHash>(address); + CScriptID id; + CRIPEMD160().Write(whash.begin(), whash.size()).Finalize(id.begin()); + CScript witnessScript; + if (provider->GetCScript(id, witnessScript)) { + entry.pushKV("witnessScript", HexStr(witnessScript.begin(), witnessScript.end())); + } } } } @@ -2978,8 +2984,11 @@ static UniValue listunspent(const JSONRPCRequest& request) entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { - auto descriptor = InferDescriptor(scriptPubKey, *pwallet->GetLegacyScriptPubKeyMan()); - entry.pushKV("desc", descriptor->ToString()); + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); + if (provider) { + auto descriptor = InferDescriptor(scriptPubKey, *provider); + entry.pushKV("desc", descriptor->ToString()); + } } if (avoid_reuse) entry.pushKV("reused", reused); entry.pushKV("safe", out.fSafe); @@ -3288,7 +3297,23 @@ UniValue signrawtransactionwithwallet(const JSONRPCRequest& request) // Parse the prevtxs array ParsePrevouts(request.params[1], nullptr, coins); - return SignTransaction(mtx, &*pwallet->GetLegacyScriptPubKeyMan(), coins, request.params[2]); + std::set<const SigningProvider*> providers; + for (const std::pair<COutPoint, Coin> coin_pair : coins) { + const SigningProvider* provider = pwallet->GetSigningProvider(coin_pair.second.out.scriptPubKey); + if (provider) { + providers.insert(std::move(provider)); + } + } + if (providers.size() == 0) { + // When there are no available providers, use DUMMY_SIGNING_PROVIDER so we can check if the tx is complete + providers.insert(&DUMMY_SIGNING_PROVIDER); + } + + UniValue result(UniValue::VOBJ); + for (const SigningProvider* provider : providers) { + SignTransaction(mtx, provider, coins, request.params[2], result); + } + return result; } static UniValue bumpfee(const JSONRPCRequest& request) @@ -3340,10 +3365,11 @@ static UniValue bumpfee(const JSONRPCRequest& request) }, RPCResult{ "{\n" - " \"txid\": \"value\", (string) The id of the new transaction\n" - " \"origfee\": n, (numeric) Fee of the replaced transaction\n" - " \"fee\": n, (numeric) Fee of the new transaction\n" - " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty)\n" + " \"psbt\": \"psbt\", (string) The base64-encoded unsigned PSBT of the new transaction. Only returned when wallet private keys are disabled.\n" + " \"txid\": \"value\", (string) The id of the new transaction. Only returned when wallet private keys are enabled.\n" + " \"origfee\": n, (numeric) The fee of the replaced transaction.\n" + " \"fee\": n, (numeric) The fee of the new transaction.\n" + " \"errors\": [ str... ] (json array of strings) Errors encountered during processing (may be empty).\n" "}\n" }, RPCExamples{ @@ -3355,10 +3381,12 @@ static UniValue bumpfee(const JSONRPCRequest& request) RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); uint256 hash(ParseHashV(request.params[0], "txid")); + CCoinControl coin_control; + coin_control.fAllowWatchOnly = pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); // optional parameters CAmount totalFee = 0; - CCoinControl coin_control; coin_control.m_signal_bip125_rbf = true; + if (!request.params[1].isNull()) { UniValue options = request.params[1]; RPCTypeCheckObj(options, @@ -3443,17 +3471,32 @@ static UniValue bumpfee(const JSONRPCRequest& request) } } - // sign bumped transaction - if (!feebumper::SignTransaction(*pwallet, mtx)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); - } - // commit the bumped transaction - uint256 txid; - if (feebumper::CommitTransaction(*pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) { - throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); - } UniValue result(UniValue::VOBJ); - result.pushKV("txid", txid.GetHex()); + + // If wallet private keys are enabled, return the new transaction id, + // otherwise return the base64-encoded unsigned PSBT of the new transaction. + if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!feebumper::SignTransaction(*pwallet, mtx)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); + } + + uint256 txid; + if (feebumper::CommitTransaction(*pwallet, hash, std::move(mtx), errors, txid) != feebumper::Result::OK) { + throw JSONRPCError(RPC_WALLET_ERROR, errors[0]); + } + + result.pushKV("txid", txid.GetHex()); + } else { + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + const TransactionError err = FillPSBT(pwallet, psbtx, complete, SIGHASH_ALL, false /* sign */, true /* bip32derivs */); + CHECK_NONFATAL(err == TransactionError::OK); + CHECK_NONFATAL(!complete); + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + result.pushKV("psbt", EncodeBase64(ssTx.str())); + } + result.pushKV("origfee", ValueFromAmount(old_fee)); result.pushKV("fee", ValueFromAmount(new_fee)); UniValue result_errors(UniValue::VARR); @@ -3653,9 +3696,10 @@ static UniValue DescribeWalletAddress(CWallet* pwallet, const CTxDestination& de { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); + CScript script = GetScriptForDestination(dest); const SigningProvider* provider = nullptr; if (pwallet) { - provider = pwallet->GetSigningProvider(); + provider = pwallet->GetSigningProvider(script); } ret.pushKVs(detail); ret.pushKVs(boost::apply_visitor(DescribeWalletAddressVisitor(provider), dest)); @@ -3682,53 +3726,62 @@ UniValue getaddressinfo(const JSONRPCRequest& request) return NullUniValue; } + const std::string example_address = "\"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl\""; + RPCHelpMan{"getaddressinfo", - "\nReturn information about the given bitcoin address. Some information requires the address\n" - "to be in the wallet.\n", + "\nReturn information about the given bitcoin address.\n" + "Some of the information will only be present if the address is in the active wallet.\n", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for which to get information."}, }, RPCResult{ "{\n" - " \"address\" : \"address\", (string) The bitcoin address validated\n" - " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address\n" - " \"ismine\" : true|false, (boolean) If the address is yours or not\n" - " \"iswatchonly\" : true|false, (boolean) If the address is watchonly\n" - " \"solvable\" : true|false, (boolean) Whether we know how to spend coins sent to this address, ignoring the possible lack of private keys\n" - " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable)\n" - " \"isscript\" : true|false, (boolean) If the key is a script\n" - " \"ischange\" : true|false, (boolean) If the address was used for change output\n" - " \"iswitness\" : true|false, (boolean) If the address is a witness address\n" - " \"witness_version\" : version (numeric, optional) The version number of the witness program\n" - " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program\n" - " \"script\" : \"type\" (string, optional) The output script type. Only if \"isscript\" is true and the redeemscript is known. Possible types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash, witness_v0_scripthash, witness_unknown\n" - " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address\n" - " \"pubkeys\" (string, optional) Array of pubkeys associated with the known redeemscript (only if \"script\" is \"multisig\")\n" + " \"address\" : \"address\", (string) The bitcoin address validated.\n" + " \"scriptPubKey\" : \"hex\", (string) The hex-encoded scriptPubKey generated by the address.\n" + " \"ismine\" : true|false, (boolean) If the address is yours.\n" + " \"iswatchonly\" : true|false, (boolean) If the address is watchonly.\n" + " \"solvable\" : true|false, (boolean) If we know how to spend coins sent to this address, ignoring the possible lack of private keys.\n" + " \"desc\" : \"desc\", (string, optional) A descriptor for spending coins sent to this address (only when solvable).\n" + " \"isscript\" : true|false, (boolean) If the key is a script.\n" + " \"ischange\" : true|false, (boolean) If the address was used for change output.\n" + " \"iswitness\" : true|false, (boolean) If the address is a witness address.\n" + " \"witness_version\" : version (numeric, optional) The version number of the witness program.\n" + " \"witness_program\" : \"hex\" (string, optional) The hex value of the witness program.\n" + " \"script\" : \"type\" (string, optional) The output script type. Only if isscript is true and the redeemscript is known. Possible\n" + " types: nonstandard, pubkey, pubkeyhash, scripthash, multisig, nulldata, witness_v0_keyhash,\n" + " witness_v0_scripthash, witness_unknown.\n" + " \"hex\" : \"hex\", (string, optional) The redeemscript for the p2sh address.\n" + " \"pubkeys\" (array, optional) Array of pubkeys associated with the known redeemscript (only if script is multisig).\n" " [\n" - " \"pubkey\"\n" + " \"pubkey\" (string)\n" " ,...\n" " ]\n" - " \"sigsrequired\" : xxxxx (numeric, optional) Number of signatures required to spend multisig output (only if \"script\" is \"multisig\")\n" - " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key, for single-key addresses (possibly embedded in P2SH or P2WSH)\n" - " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. It includes all getaddressinfo output fields for the embedded address, excluding metadata (\"timestamp\", \"hdkeypath\", \"hdseedid\") and relation to the wallet (\"ismine\", \"iswatchonly\").\n" - " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed\n" - " \"label\" : \"label\" (string) The label associated with the address, \"\" is the default label\n" - " \"timestamp\" : timestamp, (number, optional) The creation time of the key if available in seconds since epoch (Jan 1 1970 GMT)\n" - " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath if the key is HD and available\n" - " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed\n" - " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingperint of the master key.\n" - " \"labels\" (object) Array of labels associated with the address.\n" + " \"sigsrequired\" : xxxxx (numeric, optional) The number of signatures required to spend multisig output (only if script is multisig).\n" + " \"pubkey\" : \"publickeyhex\", (string, optional) The hex value of the raw public key for single-key addresses (possibly embedded in P2SH or P2WSH).\n" + " \"embedded\" : {...}, (object, optional) Information about the address embedded in P2SH or P2WSH, if relevant and known. Includes all\n" + " getaddressinfo output fields for the embedded address, excluding metadata (timestamp, hdkeypath,\n" + " hdseedid) and relation to the wallet (ismine, iswatchonly).\n" + " \"iscompressed\" : true|false, (boolean, optional) If the pubkey is compressed.\n" + " \"label\" : \"label\" (string) The label associated with the address. Defaults to \"\". Equivalent to the label name in the labels array below.\n" + " \"timestamp\" : timestamp, (number, optional) The creation time of the key, if available, expressed in " + UNIX_EPOCH_TIME + ".\n" + " \"hdkeypath\" : \"keypath\" (string, optional) The HD keypath, if the key is HD and available.\n" + " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 of the HD seed.\n" + " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The fingerprint of the master key.\n" + " \"labels\" (json object) An array of labels associated with the address. Currently limited to one label but returned\n" + " as an array to keep the API stable if multiple labels are enabled in the future.\n" " [\n" + " \"label name\" (string) The label name. Defaults to \"\". Equivalent to the label field above.\n\n" + " DEPRECATED, will be removed in 0.21. To re-enable, launch bitcoind with `-deprecatedrpc=labelspurpose`:\n" " { (json object of label data)\n" - " \"name\": \"labelname\" (string) The label\n" - " \"purpose\": \"string\" (string) Purpose of address (\"send\" for sending address, \"receive\" for receiving address)\n" - " },...\n" + " \"name\" : \"label name\" (string) The label name. Defaults to \"\". Equivalent to the label field above.\n" + " \"purpose\" : \"purpose\" (string) The purpose of the associated address (send or receive).\n" + " }\n" " ]\n" "}\n" }, RPCExamples{ - HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") - + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleCli("getaddressinfo", example_address) + + HelpExampleRpc("getaddressinfo", example_address) }, }.Check(request); @@ -3736,7 +3789,6 @@ UniValue getaddressinfo(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); CTxDestination dest = DecodeDestination(request.params[0].get_str()); - // Make sure the destination is valid if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); @@ -3747,24 +3799,34 @@ UniValue getaddressinfo(const JSONRPCRequest& request) CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); - const SigningProvider* provider = pwallet->GetSigningProvider(); + + const SigningProvider* provider = pwallet->GetSigningProvider(scriptPubKey); isminetype mine = pwallet->IsMine(dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); - bool solvable = IsSolvable(*provider, scriptPubKey); + + bool solvable = provider && IsSolvable(*provider, scriptPubKey); ret.pushKV("solvable", solvable); + if (solvable) { ret.pushKV("desc", InferDescriptor(scriptPubKey, *provider)->ToString()); } + ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); + UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); + + // Return label field if existing. Currently only one label can be + // associated with an address, so the label should be equivalent to the + // value of the name key/value pair in the labels array below. if (pwallet->mapAddressBook.count(dest)) { ret.pushKV("label", pwallet->mapAddressBook[dest].name); } + ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); - ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(); + ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); if (spk_man) { if (const CKeyMetadata* meta = spk_man->GetMetadata(dest)) { ret.pushKV("timestamp", meta->nCreateTime); @@ -3776,13 +3838,22 @@ UniValue getaddressinfo(const JSONRPCRequest& request) } } - // Currently only one label can be associated with an address, return an array - // so the API remains stable if we allow multiple labels to be associated with - // an address. + // Return a `labels` array containing the label associated with the address, + // equivalent to the `label` field above. Currently only one label can be + // associated with an address, but we return an array so the API remains + // stable if we allow multiple labels to be associated with an address in + // the future. + // + // DEPRECATED: The previous behavior of returning an array containing a JSON + // object of `name` and `purpose` key/value pairs has been deprecated. UniValue labels(UniValue::VARR); std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest); if (mi != pwallet->mapAddressBook.end()) { - labels.push_back(AddressBookDataToJSON(mi->second, true)); + if (pwallet->chain().rpcEnableDeprecated("labelspurpose")) { + labels.push_back(AddressBookDataToJSON(mi->second, true)); + } else { + labels.push_back(mi->second.name); + } } ret.pushKV("labels", std::move(labels)); diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp index 3eaaf3786c..be8a71da97 100644 --- a/src/wallet/scriptpubkeyman.cpp +++ b/src/wallet/scriptpubkeyman.cpp @@ -14,11 +14,10 @@ bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { error.clear(); - TopUp(); // Generate a new key that is added to wallet CPubKey new_key; - if (!GetKeyFromPool(new_key)) { + if (!GetKeyFromPool(new_key, type)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } @@ -202,12 +201,11 @@ isminetype LegacyScriptPubKeyMan::IsMine(const CScript& script) const assert(false); } -bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +bool LegacyScriptPubKeyMan::CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys) { { LOCK(cs_KeyStore); - if (!SetCrypted()) - return false; + assert(mapKeys.empty()); bool keyPass = mapCryptedKeys.empty(); // Always pass when there are no encrypted keys bool keyFail = false; @@ -217,7 +215,7 @@ bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) const CPubKey &vchPubKey = (*mi).second.first; const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; CKey key; - if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) + if (!DecryptKey(master_key, vchCryptedSecret, vchPubKey, key)) { keyFail = true; break; @@ -233,53 +231,55 @@ bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) } if (keyFail || (!keyPass && !accept_no_keys)) return false; - vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; } - NotifyStatusChanged(this); return true; } -bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial& vMasterKeyIn) +bool LegacyScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { + AssertLockHeld(cs_wallet); LOCK(cs_KeyStore); - if (!mapCryptedKeys.empty() || IsCrypted()) + encrypted_batch = batch; + if (!mapCryptedKeys.empty()) { + encrypted_batch = nullptr; return false; + } - fUseCrypto = true; - for (const KeyMap::value_type& mKey : mapKeys) + KeyMap keys_to_encrypt; + keys_to_encrypt.swap(mapKeys); // Clear mapKeys so AddCryptedKeyInner will succeed. + for (const KeyMap::value_type& mKey : keys_to_encrypt) { const CKey &key = mKey.second; CPubKey vchPubKey = key.GetPubKey(); CKeyingMaterial vchSecret(key.begin(), key.end()); std::vector<unsigned char> vchCryptedSecret; - if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) + if (!EncryptSecret(master_key, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) { + encrypted_batch = nullptr; return false; - if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) + } + if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) { + encrypted_batch = nullptr; return false; + } } - mapKeys.clear(); + encrypted_batch = nullptr; return true; } -bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) +bool LegacyScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { + if (!CanGetAddresses(internal)) { + return false; + } + if (!ReserveKeyFromKeyPool(index, keypool, internal)) { return false; } + address = GetDestinationForKey(keypool.vchPubKey, type); return true; } -void LegacyScriptPubKeyMan::KeepDestination(int64_t index) -{ - KeepKey(index); -} - -void LegacyScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) -{ - ReturnKey(index, internal, pubkey); -} - void LegacyScriptPubKeyMan::MarkUnusedAddresses(const CScript& script) { AssertLockHeld(cs_wallet); @@ -460,7 +460,7 @@ size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() unsigned int LegacyScriptPubKeyMan::GetKeyPoolSize() const { AssertLockHeld(cs_wallet); - return setInternalKeyPool.size() + setExternalKeyPool.size(); + return setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(); } int64_t LegacyScriptPubKeyMan::GetTimeFirstKey() const @@ -548,7 +548,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch& batch, const CKey& s RemoveWatchOnly(script); } - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return batch.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); @@ -589,7 +589,7 @@ void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID& script_id, const bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pubkey) { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::AddKeyPubKey(key, pubkey); } @@ -599,7 +599,7 @@ bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey& key, const CPubKey &pu std::vector<unsigned char> vchCryptedSecret; CKeyingMaterial vchSecret(key.begin(), key.end()); - if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { + if (!EncryptSecret(m_storage.GetEncryptionKey(), vchSecret, pubkey.GetHash(), vchCryptedSecret)) { return false; } @@ -617,9 +617,7 @@ bool LegacyScriptPubKeyMan::LoadCryptedKey(const CPubKey &vchPubKey, const std:: bool LegacyScriptPubKeyMan::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<unsigned char> &vchCryptedSecret) { LOCK(cs_KeyStore); - if (!SetCrypted()) { - return false; - } + assert(mapKeys.empty()); mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); ImplicitlyLearnRelatedKeyScripts(vchPubKey); @@ -746,7 +744,7 @@ void LegacyScriptPubKeyMan::SetHDChain(const CHDChain& chain, bool memonly) bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::HaveKey(address); } return mapCryptedKeys.count(address) > 0; @@ -755,7 +753,7 @@ bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::GetKey(address, keyOut); } @@ -764,7 +762,7 @@ bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey& keyOut) const { const CPubKey &vchPubKey = (*mi).second.first; const std::vector<unsigned char> &vchCryptedSecret = (*mi).second.second; - return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); + return DecryptKey(m_storage.GetEncryptionKey(), vchCryptedSecret, vchPubKey, keyOut); } return false; } @@ -802,7 +800,7 @@ bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address, CPubKey &pubke bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address, CPubKey& vchPubKeyOut) const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { return GetWatchPubKey(address, vchPubKeyOut); } @@ -1092,15 +1090,20 @@ void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey& pubkey, const m_pool_key_to_index[pubkey.GetID()] = index; } -void LegacyScriptPubKeyMan::KeepKey(int64_t nIndex) +void LegacyScriptPubKeyMan::KeepDestination(int64_t nIndex, const OutputType& type) { // Remove from key pool WalletBatch batch(m_storage.GetDatabase()); batch.ErasePool(nIndex); + CPubKey pubkey; + bool have_pk = GetPubKey(m_index_to_reserved_key.at(nIndex), pubkey); + assert(have_pk); + LearnRelatedScripts(pubkey, type); + m_index_to_reserved_key.erase(nIndex); WalletLogPrintf("keypool keep %d\n", nIndex); } -void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey) +void LegacyScriptPubKeyMan::ReturnDestination(int64_t nIndex, bool fInternal, const CTxDestination&) { // Return to key pool { @@ -1112,13 +1115,15 @@ void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal, const CPub } else { setExternalKeyPool.insert(nIndex); } - m_pool_key_to_index[pubkey.GetID()] = nIndex; + CKeyID& pubkey_id = m_index_to_reserved_key.at(nIndex); + m_pool_key_to_index[pubkey_id] = nIndex; + m_index_to_reserved_key.erase(nIndex); NotifyCanGetAddressesChanged(); } WalletLogPrintf("keypool return %d\n", nIndex); } -bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) +bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, const OutputType type, bool internal) { if (!CanGetAddresses(internal)) { return false; @@ -1134,7 +1139,7 @@ bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey& result, bool internal) result = GenerateNewKey(batch, internal); return true; } - KeepKey(nIndex); + KeepDestination(nIndex, type); result = keypool.vchPubKey; } return true; @@ -1147,8 +1152,6 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key { LOCK(cs_wallet); - TopUp(); - bool fReturningInternal = fRequestedInternal; fReturningInternal &= (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) || m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); bool use_split_keypool = set_pre_split_keypool.empty(); @@ -1179,6 +1182,8 @@ bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& key throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); } + assert(m_index_to_reserved_key.count(nIndex) == 0); + m_index_to_reserved_key[nIndex] = keypool.vchPubKey.GetID(); m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); WalletLogPrintf("keypool reserve %d\n", nIndex); } @@ -1379,7 +1384,7 @@ bool LegacyScriptPubKeyMan::ImportScriptPubKeys(const std::set<CScript>& script_ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const { LOCK(cs_KeyStore); - if (!IsCrypted()) { + if (!m_storage.HasEncryptionKeys()) { return FillableSigningProvider::GetKeys(); } std::set<CKeyID> set_address; @@ -1393,13 +1398,8 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet& wallet) : ScriptPubKeyMan(wallet), m_wallet(wallet), - cs_wallet(wallet.cs_wallet), - vMasterKey(wallet.vMasterKey), - fUseCrypto(wallet.fUseCrypto), - fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {} + cs_wallet(wallet.cs_wallet) {} -bool LegacyScriptPubKeyMan::SetCrypted() { return m_wallet.SetCrypted(); } -bool LegacyScriptPubKeyMan::IsCrypted() const { return m_wallet.IsCrypted(); } void LegacyScriptPubKeyMan::NotifyWatchonlyChanged(bool fHaveWatchOnly) const { return m_wallet.NotifyWatchonlyChanged(fHaveWatchOnly); } void LegacyScriptPubKeyMan::NotifyCanGetAddressesChanged() const { return m_wallet.NotifyCanGetAddressesChanged(); } template<typename... Params> void LegacyScriptPubKeyMan::WalletLogPrintf(const std::string& fmt, const Params&... parameters) const { return m_wallet.WalletLogPrintf(fmt, parameters...); } diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h index 4f17156792..b78494921c 100644 --- a/src/wallet/scriptpubkeyman.h +++ b/src/wallet/scriptpubkeyman.h @@ -31,6 +31,8 @@ public: virtual void UnsetBlankWalletFlag(WalletBatch&) = 0; virtual bool CanSupportFeature(enum WalletFeature) const = 0; virtual void SetMinVersion(enum WalletFeature, WalletBatch* = nullptr, bool = false) = 0; + virtual const CKeyingMaterial& GetEncryptionKey() const = 0; + virtual bool HasEncryptionKeys() const = 0; virtual bool IsLocked() const = 0; }; @@ -150,10 +152,18 @@ public: virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) { return false; } virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; } - virtual bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) { return false; } - virtual void KeepDestination(int64_t index) {} - virtual void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) {} + //! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it. + virtual bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) { return false; } + virtual bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) { return false; } + virtual bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) { return false; } + virtual void KeepDestination(int64_t index, const OutputType& type) {} + virtual void ReturnDestination(int64_t index, bool internal, const CTxDestination& addr) {} + + /** Fills internal address pool. Use within ScriptPubKeyMan implementations should be used sparingly and only + * when something from the address pool is removed, excluding GetNewDestination and GetReservedDestination. + * External wallet code is primarily responsible for topping up prior to fetching new addresses + */ virtual bool TopUp(unsigned int size = 0) { return false; } //! Mark unused addresses as being used @@ -193,6 +203,9 @@ public: class LegacyScriptPubKeyMan : public ScriptPubKeyMan, public FillableSigningProvider { private: + //! keeps track of whether Unlock has run a thorough check before + bool fDecryptionThoroughlyChecked = false; + using WatchOnlySet = std::set<CScript>; using WatchKeyMap = std::map<CKeyID, CPubKey>; @@ -246,9 +259,11 @@ private: std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; + // Tracks keypool indexes to CKeyIDs of keys that have been taken out of the keypool but may be returned to it + std::map<int64_t, CKeyID> m_index_to_reserved_key; //! Fetches a key from the keypool - bool GetKeyFromPool(CPubKey &key, bool internal = false); + bool GetKeyFromPool(CPubKey &key, const OutputType type, bool internal = false); /** * Reserves a key from the keypool and sets nIndex to its index @@ -266,19 +281,16 @@ private: */ bool ReserveKeyFromKeyPool(int64_t& nIndex, CKeyPool& keypool, bool fRequestedInternal); - void KeepKey(int64_t nIndex); - void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey& pubkey); - public: bool GetNewDestination(const OutputType type, CTxDestination& dest, std::string& error) override; isminetype IsMine(const CScript& script) const override; - //! will encrypt previously unencrypted keys - bool EncryptKeys(CKeyingMaterial& vMasterKeyIn); + bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override; + bool Encrypt(const CKeyingMaterial& master_key, WalletBatch* batch) override; - bool GetReservedDestination(const OutputType type, bool internal, int64_t& index, CKeyPool& keypool) override; - void KeepDestination(int64_t index) override; - void ReturnDestination(int64_t index, bool internal, const CPubKey& pubkey) override; + bool GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool) override; + void KeepDestination(int64_t index, const OutputType& type) override; + void ReturnDestination(int64_t index, bool internal, const CTxDestination&) override; bool TopUp(unsigned int size = 0) override; @@ -404,16 +416,11 @@ public: friend class CWallet; friend class ReserveDestination; LegacyScriptPubKeyMan(CWallet& wallet); - bool SetCrypted(); - bool IsCrypted() const; void NotifyWatchonlyChanged(bool fHaveWatchOnly) const; void NotifyCanGetAddressesChanged() const; template<typename... Params> void WalletLogPrintf(const std::string& fmt, const Params&... parameters) const; CWallet& m_wallet; CCriticalSection& cs_wallet; - CKeyingMaterial& vMasterKey GUARDED_BY(cs_KeyStore); - std::atomic<bool>& fUseCrypto; - bool& fDecryptionThoroughlyChecked; }; #endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp index bc068f1499..7e9f88f6b7 100644 --- a/src/wallet/test/coinselector_tests.cpp +++ b/src/wallet/test/coinselector_tests.cpp @@ -55,7 +55,7 @@ static void add_coin(const CAmount& nValue, int nInput, CoinSet& set) set.emplace(MakeTransactionRef(tx), nInput); } -static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) +static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) { balance += nValue; static int nextLockTime = 0; @@ -63,21 +63,31 @@ static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = fa tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.vout.resize(nInput + 1); tx.vout[nInput].nValue = nValue; + if (spendable) { + CTxDestination dest; + std::string error; + assert(wallet.GetNewDestination(OutputType::BECH32, "", 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); } - std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); + std::unique_ptr<CWalletTx> wtx = MakeUnique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx))); if (fIsFromMe) { wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); } COutput output(wtx.get(), nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); vCoins.push_back(output); - testWallet.AddToWallet(*wtx.get()); + wallet.AddToWallet(*wtx.get()); wtxn.emplace_back(std::move(wtx)); } +static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) +{ + add_coin(testWallet, nValue, nAge, fIsFromMe, nInput, spendable); +} static void empty_wallet(void) { @@ -252,17 +262,33 @@ BOOST_AUTO_TEST_CASE(bnb_search_test) vCoins.at(0).nInputBytes = 40; // Make sure that it has a negative effective value. The next check should assert if this somehow got through. Otherwise it will fail BOOST_CHECK(!testWallet.SelectCoinsMinConf( 1 * CENT, filter_standard, GroupCoins(vCoins), setCoinsRet, nValueRet, coin_selection_params_bnb, bnb_used)); - // Make sure that we aren't using BnB when there are preset inputs + // Test fees subtracted from output: + empty_wallet(); + add_coin(1 * CENT); + vCoins.at(0).nInputBytes = 40; + BOOST_CHECK(!testWallet.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(testWallet.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(); - add_coin(5 * CENT); - add_coin(3 * CENT); - add_coin(2 * CENT); - CCoinControl coin_control; - coin_control.fAllowOtherInputs = true; - coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); - BOOST_CHECK(testWallet.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); + { + std::unique_ptr<CWallet> wallet = MakeUnique<CWallet>(m_chain.get(), WalletLocation(), WalletDatabase::CreateMock()); + bool firstRun; + wallet->LoadWallet(firstRun); + LOCK(wallet->cs_wallet); + 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->GetHash(), vCoins.at(0).i)); + coin_selection_params_bnb.effective_fee = CFeeRate(0); + 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) diff --git a/src/wallet/test/init_test_fixture.cpp b/src/wallet/test/init_test_fixture.cpp index 86ba0013fe..797a0d634f 100644 --- a/src/wallet/test/init_test_fixture.cpp +++ b/src/wallet/test/init_test_fixture.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2018 The Bitcoin Core developers +// Copyright (c) 2018-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. diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index ff182c847f..559b26aa84 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -47,6 +47,7 @@ static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10; static CCriticalSection 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) { @@ -89,6 +90,13 @@ std::shared_ptr<CWallet> GetWallet(const std::string& name) 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; @@ -532,8 +540,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) { LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; - assert(!encrypted_batch); - encrypted_batch = new WalletBatch(*database); + WalletBatch* encrypted_batch = new WalletBatch(*database); if (!encrypted_batch->TxnBegin()) { delete encrypted_batch; encrypted_batch = nullptr; @@ -542,7 +549,7 @@ bool CWallet::EncryptWallet(const SecureString& strWalletPassphrase) encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); if (auto spk_man = m_spk_man.get()) { - if (!spk_man->EncryptKeys(_vMasterKey)) { + if (!spk_man->Encrypt(_vMasterKey, encrypted_batch)) { encrypted_batch->TxnAbort(); delete encrypted_batch; encrypted_batch = nullptr; @@ -719,17 +726,33 @@ void CWallet::SetUsedDestinationState(WalletBatch& batch, const uint256& hash, u } } -bool CWallet::IsUsedDestination(const CTxDestination& dst) const -{ - LOCK(cs_wallet); - return IsMine(dst) && GetDestData(dst, "used", nullptr); -} - bool CWallet::IsUsedDestination(const uint256& hash, unsigned int n) const { + AssertLockHeld(cs_wallet); CTxDestination dst; const CWalletTx* srctx = GetWalletTx(hash); - return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst); + if (srctx) { + assert(srctx->tx->vout.size() > n); + LegacyScriptPubKeyMan* spk_man = GetLegacyScriptPubKeyMan(); + // When descriptor wallets arrive, these additional checks are + // likely superfluous and can be optimized out + assert(spk_man != nullptr); + for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) { + WitnessV0KeyHash wpkh_dest(keyid); + if (GetDestData(wpkh_dest, "used", nullptr)) { + return true; + } + ScriptHash sh_wpkh_dest(wpkh_dest); + if (GetDestData(sh_wpkh_dest, "used", nullptr)) { + return true; + } + PKHash pkh_dest(keyid); + if (GetDestData(pkh_dest, "used", nullptr)) { + return true; + } + } + } + return false; } bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) @@ -822,7 +845,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) void CWallet::LoadToWallet(CWalletTx& wtxIn) { - // If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken. + // If wallet doesn't have a chain (e.g bitcoin-wallet), lock can't be taken. auto locked_chain = LockChain(); if (locked_chain) { Optional<int> block_height = locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock); @@ -1363,7 +1386,11 @@ bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig const CScript& scriptPubKey = txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(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; @@ -1444,11 +1471,9 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array, and the ability to sign. for (const CTxIn& input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.hash); + // Can not estimate size without knowing the input details if (mi == wallet->mapWallet.end()) { return -1; } @@ -1463,8 +1488,6 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. return -1; } return GetVirtualTransactionSize(CTransaction(txNew)); @@ -2125,7 +2148,7 @@ void CWallet::AvailableCoins(interfaces::Chain::Lock& locked_chain, std::vector< continue; } - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(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)); @@ -2160,7 +2183,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch for (const COutput& coin : availableCoins) { CTxDestination address; - if (coin.fSpendable && + 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)); } @@ -2168,12 +2191,16 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Ch 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 COutPoint& output : lockedCoins) { auto it = mapWallet.find(output.hash); if (it != mapWallet.end()) { int depth = it->second.GetDepthInMainChain(); if (depth >= 0 && output.n < it->second.tx->vout.size() && - IsMine(it->second.tx->vout[output.n]) == ISMINE_SPENDABLE) { + IsMine(it->second.tx->vout[output.n]) == is_mine_filter + ) { CTxDestination address; if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { result[address].emplace_back( @@ -2234,7 +2261,11 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil if (effective_value > 0) { group.fee += coin.m_input_bytes < 0 ? 0 : coin_selection_params.effective_fee.GetFee(coin.m_input_bytes); group.long_term_fee += coin.m_input_bytes < 0 ? 0 : long_term_feerate.GetFee(coin.m_input_bytes); - group.effective_value += effective_value; + 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); @@ -2260,13 +2291,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params, bool& bnb_used) const { std::vector<COutput> vCoins(vAvailableCoins); + CAmount 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) { - // We didn't use BnB here, so set it to false. - bnb_used = false; - for (const COutput& out : vCoins) { if (!out.fSpendable) @@ -2285,22 +2317,30 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm coin_control.ListSelected(vPresetInputs); for (const COutPoint& outpoint : vPresetInputs) { - // For now, don't use BnB if preset inputs are selected. TODO: Enable this later - bnb_used = false; - coin_selection_params.use_bnb = false; - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); if (it != mapWallet.end()) { const CWalletTx& wtx = it->second; // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) + if (wtx.tx->vout.size() <= outpoint.n) { return false; + } // Just to calculate the marginal byte size - nValueFromPresetInputs += wtx.tx->vout[outpoint.n].nValue; - setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.n)); - } else + CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + nValueFromPresetInputs += coin.txout.nValue; + if (coin.m_input_bytes <= 0) { + return false; // Not solvable, can't estimate size for fee + } + 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 @@ -2329,14 +2369,14 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); - bool res = nTargetValue <= nValueFromPresetInputs || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 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(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || - (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); + bool res = value_to_select <= 0 || + 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); @@ -2362,8 +2402,9 @@ bool CWallet::SignTransaction(CMutableTransaction& tx) const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider) { + // We don't know about this scriptpbuKey; return false; } @@ -2519,7 +2560,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std int& nChangePosInOut, std::string& strFailReason, const CCoinControl& coin_control, bool sign) { CAmount nValue = 0; - ReserveDestination reservedest(this); + 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) @@ -2578,8 +2620,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std return false; } CTxDestination dest; - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - bool ret = reservedest.GetReservedDestination(change_type, dest, true); + bool ret = reservedest.GetReservedDestination(dest, true); if (!ret) { strFailReason = "Keypool ran out, please call keypoolrefill first"; @@ -2602,7 +2643,8 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std // 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 = nSubtractFeeFromAmount == 0; // If we are doing subtract fee from recipient, then don't use BnB + coin_selection_params.use_bnb = true; + coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values // Start with no fee and loop until there is enough fee while (true) { @@ -2616,7 +2658,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std nValueToSelect += nFeeRet; // vouts to the payees - coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + } for (const auto& recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); @@ -2633,7 +2677,9 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } } // Include the fee cost for outputs. Note this is only used for BnB right now - coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + } if (IsDust(txout, chain().relayDustFee())) { @@ -2652,7 +2698,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std } // Choose coins to use - bool bnb_used; + bool bnb_used = false; if (pick_new_inputs) { nValueIn = 0; setCoins.clear(); @@ -2825,7 +2871,7 @@ bool CWallet::CreateTransaction(interfaces::Chain::Lock& locked_chain, const std const CScript& scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; - const SigningProvider* provider = GetSigningProvider(); + const SigningProvider* provider = GetSigningProvider(scriptPubKey); if (!provider || !ProduceSignature(*provider, MutableTransactionSignatureCreator(&txNew, nIn, coin.txout.nValue, SIGHASH_ALL), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; @@ -2921,7 +2967,7 @@ DBErrors CWallet::LoadWallet(bool& fFirstRunRet) { // Even if we don't use this lock in this function, we want to preserve // lock order in LoadToWallet if query of chain state is needed to know - // tx status. If lock can't be taken (e.g wallet-tool), tx confirmation + // tx status. If lock can't be taken (e.g bitcoin-wallet), tx confirmation // status may be not reliable. auto locked_chain = LockChain(); LOCK(cs_wallet); @@ -3083,6 +3129,7 @@ bool CWallet::GetNewDestination(const OutputType type, const std::string label, bool result = false; auto spk_man = m_spk_man.get(); if (spk_man) { + spk_man->TopUp(); result = spk_man->GetNewDestination(type, dest, error); } if (result) { @@ -3096,10 +3143,8 @@ bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& des { error.clear(); - m_spk_man->TopUp(); - - ReserveDestination reservedest(this); - if (!reservedest.GetReservedDestination(type, dest, true)) { + ReserveDestination reservedest(this, type); + if (!reservedest.GetReservedDestination(dest, true)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } @@ -3265,49 +3310,43 @@ std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) co return result; } -bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination& dest, bool internal) +bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal) { m_spk_man = pwallet->GetLegacyScriptPubKeyMan(); if (!m_spk_man) { return false; } - if (!pwallet->CanGetAddresses(internal)) { - return false; - } if (nIndex == -1) { + m_spk_man->TopUp(); + CKeyPool keypool; - if (!m_spk_man->GetReservedDestination(type, internal, nIndex, keypool)) { + if (!m_spk_man->GetReservedDestination(type, internal, address, nIndex, keypool)) { return false; } - vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } - assert(vchPubKey.IsValid()); - m_spk_man->LearnRelatedScripts(vchPubKey, type); - address = GetDestinationForKey(vchPubKey, type); dest = address; return true; } void ReserveDestination::KeepDestination() { - if (nIndex != -1) - m_spk_man->KeepDestination(nIndex); + if (nIndex != -1) { + m_spk_man->KeepDestination(nIndex, type); + } nIndex = -1; - vchPubKey = CPubKey(); address = CNoDestination(); } void ReserveDestination::ReturnDestination() { if (nIndex != -1) { - m_spk_man->ReturnDestination(nIndex, fInternal, vchPubKey); + m_spk_man->ReturnDestination(nIndex, fInternal, address); } nIndex = -1; - vchPubKey = CPubKey(); address = CNoDestination(); } @@ -3880,7 +3919,12 @@ std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(interfaces::Chain& chain, } } - chain.loadWallet(interfaces::MakeWallet(walletInstance)); + { + LOCK(cs_wallets); + for (auto& load_wallet : g_load_wallet_fns) { + load_wallet(interfaces::MakeWallet(walletInstance)); + } + } // Register with the validation interface. It's ok to do this after rescan since we're still holding locked_chain. walletInstance->handleNotifications(); @@ -3988,15 +4032,9 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu return groups; } -bool CWallet::SetCrypted() +bool CWallet::IsCrypted() const { - LOCK(cs_KeyStore); - if (fUseCrypto) - return true; - if (!mapKeys.empty()) - return false; - fUseCrypto = true; - return true; + return HasEncryptionKeys(); } bool CWallet::IsLocked() const @@ -4010,7 +4048,7 @@ bool CWallet::IsLocked() const bool CWallet::Lock() { - if (!SetCrypted()) + if (!IsCrypted()) return false; { @@ -4022,12 +4060,32 @@ bool CWallet::Lock() return true; } -ScriptPubKeyMan* CWallet::GetScriptPubKeyMan() const +bool CWallet::Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys) +{ + { + LOCK(cs_KeyStore); + if (m_spk_man) { + if (!m_spk_man->CheckDecryptionKey(vMasterKeyIn, accept_no_keys)) { + return false; + } + } + vMasterKey = vMasterKeyIn; + } + NotifyStatusChanged(this); + return true; +} + +ScriptPubKeyMan* CWallet::GetScriptPubKeyMan(const CScript& script) const { return m_spk_man.get(); } -const SigningProvider* CWallet::GetSigningProvider() const +const SigningProvider* CWallet::GetSigningProvider(const CScript& script) const +{ + return m_spk_man.get(); +} + +const SigningProvider* CWallet::GetSigningProvider(const CScript& script, SignatureData& sigdata) const { return m_spk_man.get(); } @@ -4036,3 +4094,13 @@ LegacyScriptPubKeyMan* CWallet::GetLegacyScriptPubKeyMan() const { return m_spk_man.get(); } + +const CKeyingMaterial& CWallet::GetEncryptionKey() const +{ + return vMasterKey; +} + +bool CWallet::HasEncryptionKeys() const +{ + return !mapMasterKeys.empty(); +} diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a6b9c0131a..c86367dc0f 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -35,6 +35,8 @@ #include <boost/signals2/signal.hpp> +using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wallet)>; + //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. @@ -48,6 +50,7 @@ bool HasWallets(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string& name); std::shared_ptr<CWallet> LoadWallet(interfaces::Chain& chain, const WalletLocation& location, std::string& error, std::vector<std::string>& warnings); +std::unique_ptr<interfaces::Handler> HandleLoadWallet(LoadWalletFn load_wallet); enum class WalletCreationStatus { SUCCESS, @@ -71,8 +74,6 @@ static const CAmount WALLET_INCREMENTAL_RELAY_FEE = 5000; static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; -//! Default for -avoidpartialspends -static const bool DEFAULT_AVOIDPARTIALSPENDS = false; //! -txconfirmtarget default static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; //! -walletrbf default @@ -140,12 +141,12 @@ class ReserveDestination { protected: //! The wallet to reserve from - CWallet* pwallet; - LegacyScriptPubKeyMan* m_spk_man{nullptr}; + 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 public key for the address - CPubKey vchPubKey; //! The destination CTxDestination address; //! Whether this is from the internal (change output) keypool @@ -153,10 +154,9 @@ protected: public: //! Construct a ReserveDestination object. This does NOT reserve an address yet - explicit ReserveDestination(CWallet* pwalletIn) - { - pwallet = pwalletIn; - } + explicit ReserveDestination(CWallet* pwallet, OutputType type) + : pwallet(pwallet) + , type(type) { } ReserveDestination(const ReserveDestination&) = delete; ReserveDestination& operator=(const ReserveDestination&) = delete; @@ -168,7 +168,7 @@ public: } //! Reserve an address - bool GetReservedDestination(const OutputType type, CTxDestination& pubkey, bool internal); + 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 @@ -584,6 +584,8 @@ struct CoinSelectionParams size_t change_spend_size = 0; CFeeRate effective_fee = CFeeRate(0); 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() {} @@ -598,14 +600,7 @@ class CWallet final : public WalletStorage, private interfaces::Chain::Notificat private: CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); - //! if fUseCrypto is true, mapKeys must be empty - //! if fUseCrypto is false, vMasterKey must be empty - std::atomic<bool> fUseCrypto; - //! keeps track of whether Unlock has run a thorough check before - bool fDecryptionThoroughlyChecked; - - bool SetCrypted(); bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); std::atomic<bool> fAbortRescan{false}; @@ -735,9 +730,7 @@ public: /** Construct wallet with specified name and database implementation. */ CWallet(interfaces::Chain* chain, const WalletLocation& location, std::unique_ptr<WalletDatabase> database) - : fUseCrypto(false), - fDecryptionThoroughlyChecked(false), - m_chain(chain), + : m_chain(chain), m_location(location), database(std::move(database)) { @@ -747,11 +740,9 @@ public: { // Should not have slots connected at this point. assert(NotifyUnload.empty()); - delete encrypted_batch; - encrypted_batch = nullptr; } - bool IsCrypted() const { return fUseCrypto; } + bool IsCrypted() const; bool IsLocked() const override; bool Lock(); @@ -810,9 +801,8 @@ public: bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - // Whether this or any UTXO with the same CTxDestination has been spent. - bool IsUsedDestination(const CTxDestination& dst) const; - bool IsUsedDestination(const uint256& hash, unsigned int n) const; + // Whether this or any known UTXO with the same single key has been spent. + bool IsUsedDestination(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetUsedDestinationState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, bool single_coin) const; @@ -1128,10 +1118,18 @@ public: LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; - ScriptPubKeyMan* GetScriptPubKeyMan() const; - const SigningProvider* GetSigningProvider() const; + //! Get the ScriptPubKeyMan for a script + ScriptPubKeyMan* GetScriptPubKeyMan(const CScript& script) const; + + //! Get the SigningProvider for a script + const SigningProvider* GetSigningProvider(const CScript& script) const; + const SigningProvider* GetSigningProvider(const CScript& script, SignatureData& sigdata) const; + LegacyScriptPubKeyMan* GetLegacyScriptPubKeyMan() const; + const CKeyingMaterial& GetEncryptionKey() const override; + bool HasEncryptionKeys() const override; + // Temporary LegacyScriptPubKeyMan accessors and aliases. friend class LegacyScriptPubKeyMan; std::unique_ptr<LegacyScriptPubKeyMan> m_spk_man = MakeUnique<LegacyScriptPubKeyMan>(*this); @@ -1141,8 +1139,6 @@ public: LegacyScriptPubKeyMan::CryptedKeyMap& mapCryptedKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapCryptedKeys; LegacyScriptPubKeyMan::WatchOnlySet& setWatchOnly GUARDED_BY(cs_KeyStore) = m_spk_man->setWatchOnly; LegacyScriptPubKeyMan::WatchKeyMap& mapWatchKeys GUARDED_BY(cs_KeyStore) = m_spk_man->mapWatchKeys; - WalletBatch*& encrypted_batch GUARDED_BY(cs_wallet) = m_spk_man->encrypted_batch; - using CryptedKeyMap = LegacyScriptPubKeyMan::CryptedKeyMap; /** Get last block processed height */ int GetLastBlockHeight() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h index b1781d5ccf..9b97318cd3 100644 --- a/src/wallet/walletdb.h +++ b/src/wallet/walletdb.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// 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. diff --git a/src/wallet/wallettool.h b/src/wallet/wallettool.h index bd08da42d6..8ee3355f02 100644 --- a/src/wallet/wallettool.h +++ b/src/wallet/wallettool.h @@ -1,4 +1,4 @@ -// Copyright (c) 2016 The Bitcoin Core developers +// Copyright (c) 2016-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. diff --git a/src/wallet/walletutil.h b/src/wallet/walletutil.h index 044c757e68..c91c9aca96 100644 --- a/src/wallet/walletutil.h +++ b/src/wallet/walletutil.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2018 The Bitcoin Core developers +// Copyright (c) 2017-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. |