Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index 2111217ce..8aa283b5c 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,1842 +1,1823 @@
// 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>
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,
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())) {
if (pwallet->mapAddressBook.count(dest)) {
if (!strAddr.empty()) {
strAddr += ",";
}
strAddr += EncodeDestination(dest, config);
strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
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.");
}
}
+static LegacyScriptPubKeyMan &GetLegacyScriptPubKeyMan(CWallet &wallet) {
+ LegacyScriptPubKeyMan *spk_man = wallet.GetLegacyScriptPubKeyMan();
+ if (!spk_man) {
+ throw JSONRPCError(RPC_WALLET_ERROR,
+ "This type of wallet does not support this command");
+ }
+ return *spk_man;
+}
+
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",
"\nAdds 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",
{
{"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"},
},
RPCResults{},
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");
}
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ GetLegacyScriptPubKeyMan(*wallet);
WalletRescanReserver reserver(pwallet);
bool fRescan = true;
{
auto locked_chain = pwallet->chain().lock();
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();
assert(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->mapAddressBook.count(dest) == 0) {
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",
"\nStops current wallet rescan triggered by an RPC call, e.g. by an "
"importprivkey call.\n",
{},
RPCResults{},
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",
"\nAdds 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"
"\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",
{
{"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"},
},
RPCResults{},
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);
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ GetLegacyScriptPubKeyMan(*pwallet);
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();
}
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
CTxDestination dest = DecodeDestination(request.params[0].get_str(),
config.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);
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain);
}
}
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",
"\nImports 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"},
},
RPCResults{},
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;
size_t txnIndex = 0;
if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) ==
merkleBlock.header.hashMerkleRoot) {
auto locked_chain = pwallet->chain().lock();
if (locked_chain->getBlockHeight(merkleBlock.header.GetHash()) ==
nullopt) {
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");
}
txnIndex = vIndex[it - vMatch.begin()];
} else {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Something wrong with merkleblock");
}
wtx.SetConf(CWalletTx::Status::CONFIRMED, merkleBlock.header.GetHash(),
txnIndex);
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
if (pwallet->IsMine(*wtx.tx)) {
pwallet->AddToWallet(wtx, false);
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",
"\nDeletes 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"},
},
RPCResults{},
RPCExamples{HelpExampleCli("removeprunedfunds",
"\"a8d0c0184dde994a09ec054286f1ce581bebf4644"
"6a512166eae7628734ea0a5\"") +
"\nAs a JSON-RPC call\n" +
HelpExampleRpc("removeprunedfunds",
"\"a8d0c0184dde994a09ec054286f1ce581bebf4644"
"6a512166eae7628734ea0a5\"")},
}
.Check(request);
auto locked_chain = pwallet->chain().lock();
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",
"\nAdds 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"
"\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",
{
{"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"},
},
RPCResults{},
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);
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ GetLegacyScriptPubKeyMan(*wallet);
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");
}
{
auto locked_chain = pwallet->chain().lock();
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);
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain);
}
}
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",
"\nImports keys from a wallet dump file (see dumpwallet). Requires a "
"new wallet backup to include imported keys.\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO,
"The wallet file"},
},
RPCResults{},
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);
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ GetLegacyScriptPubKeyMan(*wallet);
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;
{
auto locked_chain = pwallet->chain().lock();
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");
}
Optional<int> tip_height = locked_chain->getHeight();
nTimeBegin = tip_height ? locked_chain->getBlockTime(*tip_height) : 0;
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();
assert(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);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
RPCHelpMan{
"dumpprivkey",
"\nReveals 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{"\"key\" (string) The private key\n"},
RPCExamples{HelpExampleCli("dumpprivkey", "\"myaddress\"") +
HelpExampleCli("importprivkey", "\"mykey\"") +
HelpExampleRpc("dumpprivkey", "\"myaddress\"")},
}
.Check(request);
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ LegacyScriptPubKeyMan &spk_man = GetLegacyScriptPubKeyMan(*wallet);
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
std::string strAddress = request.params[0].get_str();
CTxDestination dest =
DecodeDestination(strAddress, config.GetChainParams());
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
"Invalid Bitcoin address");
}
- auto keyid = GetKeyForDestination(*spk_man, dest);
+ 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)) {
+ 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 wallet = GetWalletForJSONRPCRequest(request);
CWallet *const pwallet = wallet.get();
if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
return NullUniValue;
}
RPCHelpMan{
"dumpwallet",
"\nDumps 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 (either absolute or relative to "
"bitcoind)"},
},
RPCResult{"{ (json object)\n"
" \"filename\" : { (string) The filename with full "
"absolute path\n"
"}\n"},
RPCExamples{HelpExampleCli("dumpwallet", "\"test\"") +
HelpExampleRpc("dumpwallet", "\"test\"")},
}
.Check(request);
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ LegacyScriptPubKeyMan &spk_man = GetLegacyScriptPubKeyMan(*wallet);
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
- AssertLockHeld(spk_man->cs_wallet);
+ AssertLockHeld(spk_man.cs_wallet);
EnsureWalletIsUnlocked(pwallet);
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();
+ const std::map<CKeyID, int64_t> &mapKeyPool = spk_man.GetAllReserveKeys();
pwallet->GetKeyBirthTimes(*locked_chain, mapKeyBirth);
- std::set<CScriptID> scripts = spk_man->GetCScripts();
+ 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()));
const Optional<int> tip_height = locked_chain->getHeight();
file << strprintf("# * Best block at time of backup was %i (%s),\n",
tip_height.value_or(-1),
tip_height
? locked_chain->getBlockHash(*tip_height).ToString()
: "(missing block hash)");
file << strprintf("# mined on %s\n",
tip_height ? FormatISO8601DateTime(
locked_chain->getBlockTime(*tip_height))
: "(missing block time)");
file << "\n";
// add the base58check encoded extended master if the wallet uses HD
- CKeyID seed_id = spk_man->GetHDChain().seed_id;
+ CKeyID seed_id = spk_man.GetHDChain().seed_id;
if (!seed_id.IsNull()) {
CKey seed;
- if (spk_man->GetKey(seed_id, 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)) {
+ if (spk_man.GetKey(keyid, key)) {
file << strprintf("%s %s ", EncodeSecret(key), strTime);
- if (GetWalletAddressesForKey(config, spk_man, pwallet, keyid,
+ if (GetWalletAddressesForKey(config, &spk_man, pwallet, 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") {
+ } 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
+ (spk_man.mapKeyMetadata[keyid].has_key_origin
? " hdkeypath=" +
WriteHDKeypath(
- spk_man->mapKeyMetadata[keyid].key_origin.path)
+ 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()) {
+ 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)) {
+ 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;
}
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");
}
assert(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->chainParams);
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.
// TODO: get private keys from descriptors too
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);
}
std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(),
std::inserter(pubkey_map, pubkey_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 &timestamp = 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)), rescanning all addresses in one-shot-only (rescan can "
"be disabled via options). Requires a new wallet backup.\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 in seconds since epoch "
"(Jan 1 1970 GMT),\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 watched even when not all private keys "
"are provided."},
{"label", RPCArg::Type::STR, /* default */ "''",
"Label to assign to the address, only allowed with "
"internal=false"},
},
},
},
"\"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{"\nResponse is an array with the same size as the input "
"that has the execution result :\n"
" [{\"success\": true}, {\"success\": true, "
"\"warnings\": [\"Ignoring irrelevant private key\"]}, "
"{\"success\": false, \"error\": {\"code\": -1, "
"\"message\": \"Internal Server Error\"}}, ...]\n"},
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});
- LegacyScriptPubKeyMan *spk_man = pwallet->GetLegacyScriptPubKeyMan();
- if (!spk_man) {
- throw JSONRPCError(RPC_WALLET_ERROR,
- "This type of wallet does not support this command");
- }
+ GetLegacyScriptPubKeyMan(*wallet);
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);
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
EnsureWalletIsUnlocked(pwallet);
// Verify all timestamps are present before importing any keys.
const Optional<int> tip_height = locked_chain->getHeight();
now =
tip_height ? locked_chain->getBlockMedianTimePast(*tip_height) : 0;
for (const UniValue &data : requests.getValues()) {
GetImportTimestamp(data, now);
}
const int64_t minimumTimestamp = 1;
if (fRescan && tip_height) {
nLowestTimestamp = locked_chain->getBlockTime(*tip_height);
} else {
fRescan = false;
}
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 */);
{
auto locked_chain = pwallet->chain().lock();
LOCK(pwallet->cs_wallet);
pwallet->ReacceptWalletTransactions(*locked_chain);
}
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;
}
// clang-format off
static const CRPCCommand commands[] = {
// category name actor (function) argNames
// ------------------- ------------------------ ---------------------- ----------
{ "wallet", "abortrescan", abortrescan, {} },
{ "wallet", "dumpprivkey", dumpprivkey, {"address"} },
{ "wallet", "dumpwallet", dumpwallet, {"filename"} },
{ "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
void RegisterDumpRPCCommands(
interfaces::Chain &chain,
std::vector<std::unique_ptr<interfaces::Handler>> &handlers) {
for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) {
handlers.emplace_back(chain.handleRpc(commands[vcidx]));
}
}
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index cea95785f..09ca8a977 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -1,1304 +1,1306 @@
// Copyright (c) 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 <config.h>
#include <key_io.h>
#include <outputtype.h>
#include <script/descriptor.h>
#include <util/bip32.h>
#include <util/strencodings.h>
#include <util/translation.h>
#include <wallet/scriptpubkeyman.h>
#include <wallet/wallet.h>
bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type,
const std::string label,
CTxDestination &dest,
std::string &error) {
LOCK(cs_wallet);
error.clear();
TopUpKeyPool();
// Generate a new key that is added to wallet
CPubKey new_key;
if (!GetKeyFromPool(new_key)) {
error = "Error: Keypool ran out, please call keypoolrefill first";
return false;
}
LearnRelatedScripts(new_key, type);
dest = GetDestinationForKey(new_key, type);
m_wallet.SetAddressBook(dest, label, "receive");
return true;
}
typedef std::vector<uint8_t> valtype;
namespace {
/**
* This is an enum that tracks the execution context of a script, similar to
* SigVersion in script/interpreter. It is separate however because we want to
* distinguish between top-level scriptPubKey execution and P2SH redeemScript
* execution (a distinction that has no impact on consensus rules).
*/
enum class IsMineSigVersion {
TOP = 0, //! scriptPubKey execution
P2SH = 1, //! P2SH redeemScript
};
/**
* This is an internal representation of isminetype + invalidity.
* Its order is significant, as we return the max of all explored
* possibilities.
*/
enum class IsMineResult {
NO = 0, //! Not ours
WATCH_ONLY = 1, //! Included in watch-only balance
SPENDABLE = 2, //! Included in all balances
INVALID = 3, //! Not spendable by anyone (P2SH inside P2SH)
};
bool HaveKeys(const std::vector<valtype> &pubkeys,
const LegacyScriptPubKeyMan &keystore) {
for (const valtype &pubkey : pubkeys) {
CKeyID keyID = CPubKey(pubkey).GetID();
if (!keystore.HaveKey(keyID)) {
return false;
}
}
return true;
}
IsMineResult IsMineInner(const LegacyScriptPubKeyMan &keystore,
const CScript &scriptPubKey,
IsMineSigVersion sigversion) {
IsMineResult ret = IsMineResult::NO;
std::vector<valtype> vSolutions;
txnouttype whichType = Solver(scriptPubKey, vSolutions);
CKeyID keyID;
switch (whichType) {
case TX_NONSTANDARD:
case TX_NULL_DATA:
break;
case TX_PUBKEY:
keyID = CPubKey(vSolutions[0]).GetID();
if (keystore.HaveKey(keyID)) {
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
case TX_PUBKEYHASH:
keyID = CKeyID(uint160(vSolutions[0]));
if (keystore.HaveKey(keyID)) {
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
case TX_SCRIPTHASH: {
if (sigversion != IsMineSigVersion::TOP) {
// P2SH inside P2SH is invalid.
return IsMineResult::INVALID;
}
CScriptID scriptID = CScriptID(uint160(vSolutions[0]));
CScript subscript;
if (keystore.GetCScript(scriptID, subscript)) {
ret = std::max(ret, IsMineInner(keystore, subscript,
IsMineSigVersion::P2SH));
}
break;
}
case TX_MULTISIG: {
// Never treat bare multisig outputs as ours (they can still be made
// watchonly-though)
if (sigversion == IsMineSigVersion::TOP) {
break;
}
// Only consider transactions "mine" if we own ALL the keys
// involved. Multi-signature transactions that are partially owned
// (somebody else has a key that can spend them) enable
// spend-out-from-under-you attacks, especially in shared-wallet
// situations.
std::vector<valtype> keys(vSolutions.begin() + 1,
vSolutions.begin() + vSolutions.size() -
1);
if (HaveKeys(keys, keystore)) {
ret = std::max(ret, IsMineResult::SPENDABLE);
}
break;
}
}
if (ret == IsMineResult::NO && keystore.HaveWatchOnly(scriptPubKey)) {
ret = std::max(ret, IsMineResult::WATCH_ONLY);
}
return ret;
}
} // namespace
isminetype LegacyScriptPubKeyMan::IsMine(const CScript &script) const {
switch (IsMineInner(*this, script, IsMineSigVersion::TOP)) {
case IsMineResult::INVALID:
case IsMineResult::NO:
return ISMINE_NO;
case IsMineResult::WATCH_ONLY:
return ISMINE_WATCH_ONLY;
case IsMineResult::SPENDABLE:
return ISMINE_SPENDABLE;
}
assert(false);
}
bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) {
{
LOCK(cs_KeyStore);
if (!SetCrypted()) {
return false;
}
// Always pass when there are no encrypted keys
bool keyPass = mapCryptedKeys.empty();
bool keyFail = false;
CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin();
for (; mi != mapCryptedKeys.end(); ++mi) {
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<uint8_t> &vchCryptedSecret = (*mi).second.second;
CKey key;
if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) {
keyFail = true;
break;
}
keyPass = true;
if (fDecryptionThoroughlyChecked) {
break;
}
}
if (keyPass && keyFail) {
LogPrintf("The wallet is probably corrupted: Some keys decrypt but "
"not all.\n");
assert(false);
}
if (keyFail || (!keyPass && !accept_no_keys)) {
return false;
}
vMasterKey = vMasterKeyIn;
fDecryptionThoroughlyChecked = true;
}
NotifyStatusChanged(this);
return true;
}
bool LegacyScriptPubKeyMan::EncryptKeys(CKeyingMaterial &vMasterKeyIn) {
LOCK(cs_KeyStore);
if (!mapCryptedKeys.empty() || IsCrypted()) {
return false;
}
fUseCrypto = true;
for (const KeyMap::value_type &mKey : mapKeys) {
const CKey &key = mKey.second;
CPubKey vchPubKey = key.GetPubKey();
CKeyingMaterial vchSecret(key.begin(), key.end());
std::vector<uint8_t> vchCryptedSecret;
if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(),
vchCryptedSecret)) {
return false;
}
if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) {
return false;
}
}
mapKeys.clear();
return true;
}
void LegacyScriptPubKeyMan::UpgradeKeyMetadata() {
AssertLockHeld(cs_wallet);
if (m_storage.IsLocked() ||
m_storage.IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) {
return;
}
auto batch = std::make_unique<WalletBatch>(m_storage.GetDatabase());
for (auto &meta_pair : mapKeyMetadata) {
CKeyMetadata &meta = meta_pair.second;
// If the hdKeypath is "s", that's the seed and it doesn't have a key
// origin
if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin &&
meta.hdKeypath != "s") {
CKey key;
GetKey(meta.hd_seed_id, key);
CExtKey masterKey;
masterKey.SetSeed(key.begin(), key.size());
// Add to map
CKeyID master_id = masterKey.key.GetPubKey().GetID();
std::copy(master_id.begin(), master_id.begin() + 4,
meta.key_origin.fingerprint);
if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) {
throw std::runtime_error("Invalid stored hdKeypath");
}
meta.has_key_origin = true;
if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) {
meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN;
}
// Write meta to wallet
CPubKey pubkey;
if (GetPubKey(meta_pair.first, pubkey)) {
batch->WriteKeyMetadata(meta, pubkey, true);
}
}
}
// write before setting the flag
batch.reset();
m_storage.SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA);
}
bool LegacyScriptPubKeyMan::IsHDEnabled() const {
return !hdChain.seed_id.IsNull();
}
bool LegacyScriptPubKeyMan::CanGetAddresses(bool internal) {
LOCK(cs_wallet);
// Check if the keypool has keys
bool keypool_has_keys;
if (internal && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
keypool_has_keys = setInternalKeyPool.size() > 0;
} else {
keypool_has_keys = KeypoolCountExternalKeys() > 0;
}
// If the keypool doesn't have keys, check if we can generate them
if (!keypool_has_keys) {
return CanGenerateKeys();
}
return keypool_has_keys;
}
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t> &setKeyPool,
WalletBatch &batch) {
if (setKeyPool.empty()) {
return GetTime();
}
CKeyPool keypool;
int64_t nIndex = *(setKeyPool.begin());
if (!batch.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) +
": read oldest key in keypool failed");
}
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
}
int64_t LegacyScriptPubKeyMan::GetOldestKeyPoolTime() {
LOCK(cs_wallet);
WalletBatch batch(m_storage.GetDatabase());
// load oldest key from keypool, get time and return
int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch);
if (IsHDEnabled() && m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch),
oldestKey);
if (!set_pre_split_keypool.empty()) {
oldestKey =
std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch),
oldestKey);
}
}
return oldestKey;
}
size_t LegacyScriptPubKeyMan::KeypoolCountExternalKeys() {
AssertLockHeld(cs_wallet);
return setExternalKeyPool.size() + set_pre_split_keypool.size();
}
/**
* Update wallet first key creation time. This should be called whenever keys
* are added to the wallet, with the oldest key creation time.
*/
void LegacyScriptPubKeyMan::UpdateTimeFirstKey(int64_t nCreateTime) {
AssertLockHeld(cs_wallet);
if (nCreateTime <= 1) {
// Cannot determine birthday information, so set the wallet birthday to
// the beginning of time.
nTimeFirstKey = 1;
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
nTimeFirstKey = nCreateTime;
}
}
bool LegacyScriptPubKeyMan::AddKeyPubKey(const CKey &secret,
const CPubKey &pubkey) {
WalletBatch batch(m_storage.GetDatabase());
return LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(batch, secret, pubkey);
}
bool LegacyScriptPubKeyMan::AddKeyPubKeyWithDB(WalletBatch &batch,
const CKey &secret,
const CPubKey &pubkey) {
+ AssertLockHeld(cs_wallet);
+
// Make sure we aren't adding private keys to private key disabled wallets
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
// FillableSigningProvider has no concept of wallet databases, but calls
// AddCryptedKey which is overridden below. To avoid flushes, the database
// handle is tunneled through to it.
bool needsDB = !encrypted_batch;
if (needsDB) {
encrypted_batch = &batch;
}
if (!AddKeyPubKeyInner(secret, pubkey)) {
if (needsDB) {
encrypted_batch = nullptr;
}
return false;
}
if (needsDB) {
encrypted_batch = nullptr;
}
// Check if we need to remove from watch-only.
CScript script;
script = GetScriptForDestination(PKHash(pubkey));
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
script = GetScriptForRawPubKey(pubkey);
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
if (!IsCrypted()) {
return batch.WriteKey(pubkey, secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
return true;
}
bool LegacyScriptPubKeyMan::LoadCScript(const CScript &redeemScript) {
/**
* A sanity check was added in pull #3843 to avoid adding redeemScripts that
* never can be redeemed. However, old wallets may still contain these. Do
* not add them to the wallet and warn.
*/
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) {
std::string strAddr =
EncodeDestination(ScriptHash(redeemScript), GetConfig());
WalletLogPrintf("%s: Warning: This wallet contains a redeemScript "
"of size %i which exceeds maximum size %i thus can "
"never be redeemed. Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE,
strAddr);
return true;
}
return FillableSigningProvider::AddCScript(redeemScript);
}
void LegacyScriptPubKeyMan::LoadKeyMetadata(const CKeyID &keyID,
const CKeyMetadata &meta) {
AssertLockHeld(cs_wallet);
UpdateTimeFirstKey(meta.nCreateTime);
mapKeyMetadata[keyID] = meta;
}
void LegacyScriptPubKeyMan::LoadScriptMetadata(const CScriptID &script_id,
const CKeyMetadata &meta) {
AssertLockHeld(cs_wallet);
UpdateTimeFirstKey(meta.nCreateTime);
m_script_metadata[script_id] = meta;
}
bool LegacyScriptPubKeyMan::AddKeyPubKeyInner(const CKey &key,
const CPubKey &pubkey) {
LOCK(cs_KeyStore);
if (!IsCrypted()) {
return FillableSigningProvider::AddKeyPubKey(key, pubkey);
}
if (m_storage.IsLocked()) {
return false;
}
std::vector<uint8_t> vchCryptedSecret;
CKeyingMaterial vchSecret(key.begin(), key.end());
if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(),
vchCryptedSecret)) {
return false;
}
if (!AddCryptedKey(pubkey, vchCryptedSecret)) {
return false;
}
return true;
}
bool LegacyScriptPubKeyMan::LoadCryptedKey(
const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) {
return AddCryptedKeyInner(vchPubKey, vchCryptedSecret);
}
bool LegacyScriptPubKeyMan::AddCryptedKeyInner(
const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) {
LOCK(cs_KeyStore);
if (!SetCrypted()) {
return false;
}
mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret);
return true;
}
bool LegacyScriptPubKeyMan::AddCryptedKey(
const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) {
if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) {
return false;
}
LOCK(cs_wallet);
if (encrypted_batch) {
return encrypted_batch->WriteCryptedKey(
vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
return WalletBatch(m_storage.GetDatabase())
.WriteCryptedKey(vchPubKey, vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
bool LegacyScriptPubKeyMan::HaveWatchOnly(const CScript &dest) const {
LOCK(cs_KeyStore);
return setWatchOnly.count(dest) > 0;
}
bool LegacyScriptPubKeyMan::HaveWatchOnly() const {
LOCK(cs_KeyStore);
return (!setWatchOnly.empty());
}
static bool ExtractPubKey(const CScript &dest, CPubKey &pubKeyOut) {
std::vector<std::vector<uint8_t>> solutions;
return Solver(dest, solutions) == TX_PUBKEY &&
(pubKeyOut = CPubKey(solutions[0])).IsFullyValid();
}
bool LegacyScriptPubKeyMan::RemoveWatchOnly(const CScript &dest) {
AssertLockHeld(cs_wallet);
{
LOCK(cs_KeyStore);
setWatchOnly.erase(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey)) {
mapWatchKeys.erase(pubKey.GetID());
}
}
if (!HaveWatchOnly()) {
NotifyWatchonlyChanged(false);
}
return WalletBatch(m_storage.GetDatabase()).EraseWatchOnly(dest);
}
bool LegacyScriptPubKeyMan::LoadWatchOnly(const CScript &dest) {
return AddWatchOnlyInMem(dest);
}
bool LegacyScriptPubKeyMan::AddWatchOnlyInMem(const CScript &dest) {
LOCK(cs_KeyStore);
setWatchOnly.insert(dest);
CPubKey pubKey;
if (ExtractPubKey(dest, pubKey)) {
mapWatchKeys[pubKey.GetID()] = pubKey;
}
return true;
}
bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch,
const CScript &dest) {
if (!AddWatchOnlyInMem(dest)) {
return false;
}
const CKeyMetadata &meta = m_script_metadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
if (batch.WriteWatchOnly(dest, meta)) {
m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
return true;
}
return false;
}
bool LegacyScriptPubKeyMan::AddWatchOnlyWithDB(WalletBatch &batch,
const CScript &dest,
int64_t create_time) {
m_script_metadata[CScriptID(dest)].nCreateTime = create_time;
return AddWatchOnlyWithDB(batch, dest);
}
bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript &dest) {
WalletBatch batch(m_storage.GetDatabase());
return AddWatchOnlyWithDB(batch, dest);
}
bool LegacyScriptPubKeyMan::AddWatchOnly(const CScript &dest,
int64_t nCreateTime) {
m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
return AddWatchOnly(dest);
}
void LegacyScriptPubKeyMan::SetHDChain(const CHDChain &chain, bool memonly) {
LOCK(cs_wallet);
if (!memonly && !WalletBatch(m_storage.GetDatabase()).WriteHDChain(chain)) {
throw std::runtime_error(std::string(__func__) +
": writing chain failed");
}
hdChain = chain;
}
bool LegacyScriptPubKeyMan::HaveKey(const CKeyID &address) const {
LOCK(cs_KeyStore);
if (!IsCrypted()) {
return FillableSigningProvider::HaveKey(address);
}
return mapCryptedKeys.count(address) > 0;
}
bool LegacyScriptPubKeyMan::GetKey(const CKeyID &address, CKey &keyOut) const {
LOCK(cs_KeyStore);
if (!IsCrypted()) {
return FillableSigningProvider::GetKey(address, keyOut);
}
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end()) {
const CPubKey &vchPubKey = (*mi).second.first;
const std::vector<uint8_t> &vchCryptedSecret = (*mi).second.second;
return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut);
}
return false;
}
bool LegacyScriptPubKeyMan::GetKeyOrigin(const CKeyID &keyID,
KeyOriginInfo &info) const {
CKeyMetadata meta;
{
LOCK(cs_wallet);
auto it = mapKeyMetadata.find(keyID);
if (it != mapKeyMetadata.end()) {
meta = it->second;
}
}
if (meta.has_key_origin) {
std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4,
info.fingerprint);
info.path = meta.key_origin.path;
} else {
// Single pubkeys get the master fingerprint of themselves
std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint);
}
return true;
}
bool LegacyScriptPubKeyMan::GetWatchPubKey(const CKeyID &address,
CPubKey &pubkey_out) const {
LOCK(cs_KeyStore);
WatchKeyMap::const_iterator it = mapWatchKeys.find(address);
if (it != mapWatchKeys.end()) {
pubkey_out = it->second;
return true;
}
return false;
}
bool LegacyScriptPubKeyMan::GetPubKey(const CKeyID &address,
CPubKey &vchPubKeyOut) const {
LOCK(cs_KeyStore);
if (!IsCrypted()) {
if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) {
return GetWatchPubKey(address, vchPubKeyOut);
}
return true;
}
CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address);
if (mi != mapCryptedKeys.end()) {
vchPubKeyOut = (*mi).second.first;
return true;
}
// Check for watch-only pubkeys
return GetWatchPubKey(address, vchPubKeyOut);
}
CPubKey LegacyScriptPubKeyMan::GenerateNewKey(WalletBatch &batch,
bool internal) {
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET));
AssertLockHeld(cs_wallet);
// default to compressed public keys if we want 0.6.0 wallets
bool fCompressed = m_storage.CanSupportFeature(FEATURE_COMPRPUBKEY);
CKey secret;
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// use HD key derivation if HD was enabled during wallet creation and a seed
// is present
if (IsHDEnabled()) {
DeriveNewChildKey(
batch, metadata, secret,
(m_storage.CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
if (fCompressed) {
m_storage.SetMinVersion(FEATURE_COMPRPUBKEY);
}
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
UpdateTimeFirstKey(nCreationTime);
if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) {
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
}
return pubkey;
}
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
void LegacyScriptPubKeyMan::DeriveNewChildKey(WalletBatch &batch,
CKeyMetadata &metadata,
CKey &secret, bool internal) {
// for now we use a fixed keypath scheme of m/0'/0'/k
// seed (256bit)
CKey seed;
// hd master key
CExtKey masterKey;
// key at m/0'
CExtKey accountKey;
// key at m/0'/0' (external) or m/0'/1' (internal)
CExtKey chainChildKey;
// key at m/0'/0'/<n>'
CExtKey childKey;
// try to get the seed
if (!GetKey(hdChain.seed_id, seed)) {
throw std::runtime_error(std::string(__func__) + ": seed not found");
}
masterKey.SetSeed(seed.begin(), seed.size());
// derive m/0'
// use hardened derivation (child keys >= 0x80000000 are hardened after
// bip32)
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
assert(internal ? m_storage.CanSupportFeature(FEATURE_HD_SPLIT) : true);
accountKey.Derive(chainChildKey,
BIP32_HARDENED_KEY_LIMIT + (internal ? 1 : 0));
// derive child key at next index, skip keys already known to the wallet
do {
// always derive hardened keys
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened
// child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
if (internal) {
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/1'/" +
std::to_string(hdChain.nInternalChainCounter) +
"'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(hdChain.nInternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
hdChain.nInternalChainCounter++;
} else {
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/0'/" +
std::to_string(hdChain.nExternalChainCounter) +
"'";
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT);
metadata.key_origin.path.push_back(hdChain.nExternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
hdChain.nExternalChainCounter++;
}
} while (HaveKey(childKey.key.GetPubKey().GetID()));
secret = childKey.key;
metadata.hd_seed_id = hdChain.seed_id;
CKeyID master_id = masterKey.key.GetPubKey().GetID();
std::copy(master_id.begin(), master_id.begin() + 4,
metadata.key_origin.fingerprint);
metadata.has_key_origin = true;
// update the chain model in the database
if (!batch.WriteHDChain(hdChain)) {
throw std::runtime_error(std::string(__func__) +
": Writing HD chain model failed");
}
}
void LegacyScriptPubKeyMan::LoadKeyPool(int64_t nIndex,
const CKeyPool &keypool) {
AssertLockHeld(cs_wallet);
if (keypool.m_pre_split) {
set_pre_split_keypool.insert(nIndex);
} else if (keypool.fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
// If no metadata exists yet, create a default with the pool key's
// creation time. Note that this may be overwritten by actually
// stored metadata for that key later, which is fine.
CKeyID keyid = keypool.vchPubKey.GetID();
if (mapKeyMetadata.count(keyid) == 0) {
mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
}
}
bool LegacyScriptPubKeyMan::CanGenerateKeys() {
// A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a
// non-HD wallet (pre FEATURE_HD)
LOCK(cs_wallet);
return IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD);
}
CPubKey LegacyScriptPubKeyMan::GenerateNewSeed() {
assert(!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
CKey key;
key.MakeNewKey(true);
return DeriveNewSeed(key);
}
CPubKey LegacyScriptPubKeyMan::DeriveNewSeed(const CKey &key) {
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// Calculate the seed
CPubKey seed = key.GetPubKey();
assert(key.VerifyPubKey(seed));
// Set the hd keypath to "s" -> Seed, refers the seed to itself
metadata.hdKeypath = "s";
metadata.has_key_origin = false;
metadata.hd_seed_id = seed.GetID();
LOCK(cs_wallet);
// mem store the metadata
mapKeyMetadata[seed.GetID()] = metadata;
// Write the key&metadata to the database
if (!AddKeyPubKey(key, seed)) {
throw std::runtime_error(std::string(__func__) +
": AddKeyPubKey failed");
}
return seed;
}
void LegacyScriptPubKeyMan::SetHDSeed(const CPubKey &seed) {
LOCK(cs_wallet);
// Store the keyid (hash160) together with the child index counter in the
// database as a hdchain object.
CHDChain newHdChain;
newHdChain.nVersion = m_storage.CanSupportFeature(FEATURE_HD_SPLIT)
? CHDChain::VERSION_HD_CHAIN_SPLIT
: CHDChain::VERSION_HD_BASE;
newHdChain.seed_id = seed.GetID();
SetHDChain(newHdChain, false);
NotifyCanGetAddressesChanged();
m_wallet.UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET);
}
/**
* Mark old keypool keys as used, and generate all new keys.
*/
bool LegacyScriptPubKeyMan::NewKeyPool() {
if (m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
return false;
}
LOCK(cs_wallet);
WalletBatch batch(m_storage.GetDatabase());
for (const int64_t nIndex : setInternalKeyPool) {
batch.ErasePool(nIndex);
}
setInternalKeyPool.clear();
for (const int64_t nIndex : setExternalKeyPool) {
batch.ErasePool(nIndex);
}
setExternalKeyPool.clear();
for (int64_t nIndex : set_pre_split_keypool) {
batch.ErasePool(nIndex);
}
set_pre_split_keypool.clear();
m_pool_key_to_index.clear();
if (!TopUpKeyPool()) {
return false;
}
WalletLogPrintf("LegacyScriptPubKeyMan::NewKeyPool rewrote keypool\n");
return true;
}
bool LegacyScriptPubKeyMan::TopUpKeyPool(unsigned int kpSize) {
if (!CanGenerateKeys()) {
return false;
}
{
LOCK(cs_wallet);
if (m_storage.IsLocked()) {
return false;
}
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0) {
nTargetSize = kpSize;
} else {
nTargetSize = std::max<int64_t>(
gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0);
}
// count amount of available keys (internal, external)
// make sure the keypool of external and internal keys fits the user
// selected target (-keypool)
int64_t missingExternal = std::max<int64_t>(
std::max<int64_t>(nTargetSize, 1) - setExternalKeyPool.size(), 0);
int64_t missingInternal = std::max<int64_t>(
std::max<int64_t>(nTargetSize, 1) - setInternalKeyPool.size(), 0);
if (!IsHDEnabled() || !m_storage.CanSupportFeature(FEATURE_HD_SPLIT)) {
// don't create extra internal keys
missingInternal = 0;
}
bool internal = false;
WalletBatch batch(m_storage.GetDatabase());
for (int64_t i = missingInternal + missingExternal; i--;) {
if (i < missingInternal) {
internal = true;
}
CPubKey pubkey(GenerateNewKey(batch, internal));
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
}
if (missingInternal + missingExternal > 0) {
WalletLogPrintf(
"keypool added %d keys (%d internal), size=%u (%u internal)\n",
missingInternal + missingExternal, missingInternal,
setInternalKeyPool.size() + setExternalKeyPool.size() +
set_pre_split_keypool.size(),
setInternalKeyPool.size());
}
}
NotifyCanGetAddressesChanged();
return true;
}
void LegacyScriptPubKeyMan::AddKeypoolPubkeyWithDB(const CPubKey &pubkey,
const bool internal,
WalletBatch &batch) {
LOCK(cs_wallet);
// How in the hell did you use so many keys?
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max());
int64_t index = ++m_max_keypool_index;
if (!batch.WritePool(index, CKeyPool(pubkey, internal))) {
throw std::runtime_error(std::string(__func__) +
": writing imported pubkey failed");
}
if (internal) {
setInternalKeyPool.insert(index);
} else {
setExternalKeyPool.insert(index);
}
m_pool_key_to_index[pubkey.GetID()] = index;
}
void LegacyScriptPubKeyMan::KeepKey(int64_t nIndex) {
// Remove from key pool.
WalletBatch batch(m_storage.GetDatabase());
batch.ErasePool(nIndex);
WalletLogPrintf("keypool keep %d\n", nIndex);
}
void LegacyScriptPubKeyMan::ReturnKey(int64_t nIndex, bool fInternal,
const CPubKey &pubkey) {
// Return to key pool
{
LOCK(cs_wallet);
if (fInternal) {
setInternalKeyPool.insert(nIndex);
} else if (!set_pre_split_keypool.empty()) {
set_pre_split_keypool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_pool_key_to_index[pubkey.GetID()] = nIndex;
NotifyCanGetAddressesChanged();
}
WalletLogPrintf("keypool return %d\n", nIndex);
}
bool LegacyScriptPubKeyMan::GetKeyFromPool(CPubKey &result, bool internal) {
if (!CanGetAddresses(internal)) {
return false;
}
CKeyPool keypool;
LOCK(cs_wallet);
int64_t nIndex;
if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) &&
!m_storage.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (m_storage.IsLocked()) {
return false;
}
WalletBatch batch(m_storage.GetDatabase());
result = GenerateNewKey(batch, internal);
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
return true;
}
bool LegacyScriptPubKeyMan::ReserveKeyFromKeyPool(int64_t &nIndex,
CKeyPool &keypool,
bool fRequestedInternal) {
nIndex = -1;
keypool.vchPubKey = CPubKey();
{
LOCK(cs_wallet);
TopUpKeyPool();
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();
std::set<int64_t> &setKeyPool =
use_split_keypool
? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool)
: set_pre_split_keypool;
// Get the oldest key
if (setKeyPool.empty()) {
return false;
}
WalletBatch batch(m_storage.GetDatabase());
auto it = setKeyPool.begin();
nIndex = *it;
setKeyPool.erase(it);
if (!batch.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed");
}
CPubKey pk;
if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) {
throw std::runtime_error(std::string(__func__) +
": unknown key in key pool");
}
// If the key was pre-split keypool, we don't care about what type it is
if (use_split_keypool && keypool.fInternal != fReturningInternal) {
throw std::runtime_error(std::string(__func__) +
": keypool entry misclassified");
}
if (!keypool.vchPubKey.IsValid()) {
throw std::runtime_error(std::string(__func__) +
": keypool entry invalid");
}
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
WalletLogPrintf("keypool reserve %d\n", nIndex);
}
NotifyCanGetAddressesChanged();
return true;
}
void LegacyScriptPubKeyMan::LearnRelatedScripts(const CPubKey &key,
OutputType type) {
// Nothing to do...
}
void LegacyScriptPubKeyMan::LearnAllRelatedScripts(const CPubKey &key) {
// Nothing to do...
}
void LegacyScriptPubKeyMan::MarkReserveKeysAsUsed(int64_t keypool_id) {
AssertLockHeld(cs_wallet);
bool internal = setInternalKeyPool.count(keypool_id);
if (!internal) {
assert(setExternalKeyPool.count(keypool_id) ||
set_pre_split_keypool.count(keypool_id));
}
std::set<int64_t> *setKeyPool =
internal ? &setInternalKeyPool
: (set_pre_split_keypool.empty() ? &setExternalKeyPool
: &set_pre_split_keypool);
auto it = setKeyPool->begin();
WalletBatch batch(m_storage.GetDatabase());
while (it != std::end(*setKeyPool)) {
const int64_t &index = *(it);
if (index > keypool_id) {
// set*KeyPool is ordered
break;
}
CKeyPool keypool;
if (batch.ReadPool(index, keypool)) {
// TODO: This should be unnecessary
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
}
LearnAllRelatedScripts(keypool.vchPubKey);
batch.ErasePool(index);
WalletLogPrintf("keypool index %d removed\n", index);
it = setKeyPool->erase(it);
}
}
std::vector<CKeyID> GetAffectedKeys(const CScript &spk,
const SigningProvider &provider) {
std::vector<CScript> dummy;
FlatSigningProvider out;
InferDescriptor(spk, provider)
->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out);
std::vector<CKeyID> ret;
for (const auto &entry : out.pubkeys) {
ret.push_back(entry.first);
}
return ret;
}
void LegacyScriptPubKeyMan::MarkPreSplitKeys() {
WalletBatch batch(m_storage.GetDatabase());
for (auto it = setExternalKeyPool.begin();
it != setExternalKeyPool.end();) {
int64_t index = *it;
CKeyPool keypool;
if (!batch.ReadPool(index, keypool)) {
throw std::runtime_error(std::string(__func__) +
": read keypool entry failed");
}
keypool.m_pre_split = true;
if (!batch.WritePool(index, keypool)) {
throw std::runtime_error(std::string(__func__) +
": writing modified keypool entry failed");
}
set_pre_split_keypool.insert(index);
it = setExternalKeyPool.erase(it);
}
}
bool LegacyScriptPubKeyMan::AddCScript(const CScript &redeemScript) {
WalletBatch batch(m_storage.GetDatabase());
return AddCScriptWithDB(batch, redeemScript);
}
bool LegacyScriptPubKeyMan::AddCScriptWithDB(WalletBatch &batch,
const CScript &redeemScript) {
if (!FillableSigningProvider::AddCScript(redeemScript)) {
return false;
}
if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) {
m_storage.UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET);
return true;
}
return false;
}
bool LegacyScriptPubKeyMan::AddKeyOriginWithDB(WalletBatch &batch,
const CPubKey &pubkey,
const KeyOriginInfo &info) {
LOCK(cs_wallet);
std::copy(info.fingerprint, info.fingerprint + 4,
mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint);
mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path;
mapKeyMetadata[pubkey.GetID()].has_key_origin = true;
mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path);
return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true);
}
bool LegacyScriptPubKeyMan::ImportScripts(const std::set<CScript> scripts,
int64_t timestamp) {
WalletBatch batch(m_storage.GetDatabase());
for (const auto &entry : scripts) {
CScriptID id(entry);
if (HaveCScript(id)) {
WalletLogPrintf("Already have script %s, skipping\n",
HexStr(entry));
continue;
}
if (!AddCScriptWithDB(batch, entry)) {
return false;
}
if (timestamp > 0) {
m_script_metadata[CScriptID(entry)].nCreateTime = timestamp;
}
}
if (timestamp > 0) {
UpdateTimeFirstKey(timestamp);
}
return true;
}
bool LegacyScriptPubKeyMan::ImportPrivKeys(
const std::map<CKeyID, CKey> &privkey_map, const int64_t timestamp) {
WalletBatch batch(m_storage.GetDatabase());
for (const auto &entry : privkey_map) {
const CKey &key = entry.second;
CPubKey pubkey = key.GetPubKey();
const CKeyID &id = entry.first;
assert(key.VerifyPubKey(pubkey));
// Skip if we already have the key
if (HaveKey(id)) {
WalletLogPrintf("Already have key with pubkey %s, skipping\n",
HexStr(pubkey));
continue;
}
mapKeyMetadata[id].nCreateTime = timestamp;
// If the private key is not present in the wallet, insert it.
if (!AddKeyPubKeyWithDB(batch, key, pubkey)) {
return false;
}
UpdateTimeFirstKey(timestamp);
}
return true;
}
bool LegacyScriptPubKeyMan::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) {
WalletBatch batch(m_storage.GetDatabase());
for (const auto &entry : key_origins) {
AddKeyOriginWithDB(batch, entry.second.first, entry.second.second);
}
for (const CKeyID &id : ordered_pubkeys) {
auto entry = pubkey_map.find(id);
if (entry == pubkey_map.end()) {
continue;
}
const CPubKey &pubkey = entry->second;
CPubKey temp;
if (GetPubKey(id, temp)) {
// Already have pubkey, skipping
WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp));
continue;
}
if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey),
timestamp)) {
return false;
}
mapKeyMetadata[id].nCreateTime = timestamp;
// Add to keypool only works with pubkeys
if (add_keypool) {
AddKeypoolPubkeyWithDB(pubkey, internal, batch);
NotifyCanGetAddressesChanged();
}
}
return true;
}
bool LegacyScriptPubKeyMan::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) {
WalletBatch batch(m_storage.GetDatabase());
for (const CScript &script : script_pub_keys) {
if (!have_solving_data || !IsMine(script)) {
// Always call AddWatchOnly for non-solvable watch-only, so that
// watch timestamp gets updated
if (!AddWatchOnlyWithDB(batch, script, timestamp)) {
return false;
}
}
CTxDestination dest;
ExtractDestination(script, dest);
if (apply_label && IsValidDestination(dest)) {
m_wallet.SetAddressBookWithDB(batch, dest, label, "receive");
}
}
return true;
}
std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const {
LOCK(cs_KeyStore);
if (!IsCrypted()) {
return FillableSigningProvider::GetKeys();
}
std::set<CKeyID> set_address;
for (const auto &mi : mapCryptedKeys) {
set_address.insert(mi.first);
}
return set_address;
}
// Temporary CWallet accessors and aliases.
LegacyScriptPubKeyMan::LegacyScriptPubKeyMan(CWallet &wallet)
: ScriptPubKeyMan(wallet), m_wallet(wallet), cs_wallet(wallet.cs_wallet),
vMasterKey(wallet.vMasterKey), fUseCrypto(wallet.fUseCrypto),
fDecryptionThoroughlyChecked(wallet.fDecryptionThoroughlyChecked) {}
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 ee80f0895..a2118761f 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -1,412 +1,412 @@
// Copyright (c) 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.
#ifndef BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#define BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
#include <script/signingprovider.h>
#include <script/standard.h>
#include <wallet/crypter.h>
#include <wallet/ismine.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
#include <boost/signals2/signal.hpp>
enum class OutputType;
// Wallet storage things that ScriptPubKeyMans need in order to be able to store
// things to the wallet database. It provides access to things that are part of
// the entire wallet and not specific to a ScriptPubKeyMan such as wallet flags,
// wallet version, encryption keys, encryption status, and the database itself.
// This allows a ScriptPubKeyMan to have callbacks into CWallet without causing
// a circular dependency. WalletStorage should be the same for all
-// ScriptPubKeyMans.
+// ScriptPubKeyMans of a wallet.
class WalletStorage {
public:
virtual ~WalletStorage() = default;
virtual const std::string GetDisplayName() const = 0;
virtual WalletDatabase &GetDatabase() = 0;
virtual bool IsWalletFlagSet(uint64_t) const = 0;
virtual void SetWalletFlag(uint64_t) = 0;
virtual void UnsetWalletFlagWithDB(WalletBatch &, uint64_t) = 0;
virtual bool CanSupportFeature(enum WalletFeature) const = 0;
virtual void SetMinVersion(enum WalletFeature, WalletBatch * = nullptr,
bool = false) = 0;
virtual bool IsLocked() const = 0;
};
//! Default for -keypool
static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000;
/**
* A key from a CWallet's keypool
*
* The wallet holds one (for pre HD-split wallets) or several keypools. These
* are sets of keys that have not yet been used to provide addresses or receive
* change.
*
* The Bitcoin ABC wallet was originally a collection of unrelated private
* keys with their associated addresses. If a non-HD wallet generated a
* key/address, gave that address out and then restored a backup from before
* that key's generation, then any funds sent to that address would be
* lost definitively.
*
* The keypool was implemented to avoid this scenario (commit: 10384941). The
* wallet would generate a set of keys (100 by default). When a new public key
* was required, either to give out as an address or to use in a change output,
* it would be drawn from the keypool. The keypool would then be topped up to
* maintain 100 keys. This ensured that as long as the wallet hadn't used more
* than 100 keys since the previous backup, all funds would be safe, since a
* restored wallet would be able to scan for all owned addresses.
*
* A keypool also allowed encrypted wallets to give out addresses without
* having to be decrypted to generate a new private key.
*
* With the introduction of HD wallets (commit: f1902510), the keypool
* essentially became an address look-ahead pool. Restoring old backups can no
* longer definitively lose funds as long as the addresses used were from the
* wallet's HD seed (since all private keys can be rederived from the seed).
* However, if many addresses were used since the backup, then the wallet may
* not know how far ahead in the HD chain to look for its addresses. The
* keypool is used to implement a 'gap limit'. The keypool maintains a set of
* keys (by default 1000) ahead of the last used key and scans for the
* addresses of those keys. This avoids the risk of not seeing transactions
* involving the wallet's addresses, or of re-using the same address.
*
* The HD-split wallet feature added a second keypool (commit: 02592f4c). There
* is an external keypool (for addresses to hand out) and an internal keypool
* (for change addresses).
*
* Keypool keys are stored in the wallet/keystore's keymap. The keypool data is
* stored as sets of indexes in the wallet (setInternalKeyPool,
* setExternalKeyPool and set_pre_split_keypool), and a map from the key to the
* index (m_pool_key_to_index). The CKeyPool object is used to
* serialize/deserialize the pool data to/from the database.
*/
class CKeyPool {
public:
//! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB
int64_t nTime;
//! The public key
CPubKey vchPubKey;
//! Whether this keypool entry is in the internal keypool (for change
//! outputs)
bool fInternal;
//! Whether this key was generated for a keypool before the wallet was
//! upgraded to HD-split
bool m_pre_split;
CKeyPool();
CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn);
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
int nVersion = s.GetVersion();
if (!(s.GetType() & SER_GETHASH)) {
READWRITE(nVersion);
}
READWRITE(nTime);
READWRITE(vchPubKey);
if (ser_action.ForRead()) {
try {
READWRITE(fInternal);
} catch (std::ios_base::failure &) {
/**
* flag as external address if we can't read the internal
* boolean (this will be the case for any wallet before the HD
* chain split version)
*/
fInternal = false;
}
try {
READWRITE(m_pre_split);
} catch (std::ios_base::failure &) {
/**
* flag as postsplit address if we can't read the m_pre_split
* boolean (this will be the case for any wallet that upgrades
* to HD chain split)
*/
m_pre_split = false;
}
} else {
READWRITE(fInternal);
READWRITE(m_pre_split);
}
}
};
/*
* A class implementing ScriptPubKeyMan manages some (or all) scriptPubKeys used
* in a wallet. It contains the scripts and keys related to the scriptPubKeys it
* manages. A ScriptPubKeyMan will be able to give out scriptPubKeys to be used,
* as well as marking when a scriptPubKey has been used. It also handles when
* and how to store a scriptPubKey and its related scripts and keys, including
* encryption.
*/
class ScriptPubKeyMan {
protected:
WalletStorage &m_storage;
public:
ScriptPubKeyMan(WalletStorage &storage) : m_storage(storage) {}
};
class LegacyScriptPubKeyMan : public ScriptPubKeyMan,
public FillableSigningProvider {
private:
using CryptedKeyMap =
std::map<CKeyID, std::pair<CPubKey, std::vector<uint8_t>>>;
using WatchOnlySet = std::set<CScript>;
using WatchKeyMap = std::map<CKeyID, CPubKey>;
//! will encrypt previously unencrypted keys
bool EncryptKeys(CKeyingMaterial &vMasterKeyIn);
CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore);
WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore);
WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore);
bool AddCryptedKeyInner(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret);
bool AddKeyPubKeyInner(const CKey &key, const CPubKey &pubkey);
WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr;
/* the HD chain data model (external chain counters) */
CHDChain hdChain;
/* HD derive new child key (on internal or external chain) */
void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata &metadata,
CKey &secret, bool internal = false)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet);
std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet);
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;
int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0;
/**
* Private version of AddWatchOnly method which does not accept a
* timestamp, and which will reset the wallet's nTimeFirstKey value to 1 if
* the watch key did not previously have a timestamp associated with it.
* Because this is an inherited virtual method, it is accessible despite
* being marked private, but it is marked private anyway to encourage use
* of the other AddWatchOnly which accepts a timestamp and sets
* nTimeFirstKey more intelligently for more efficient rescans.
*/
bool AddWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool AddWatchOnlyInMem(const CScript &dest);
/** Add a KeyOriginInfo to the wallet */
bool AddKeyOriginWithDB(WalletBatch &batch, const CPubKey &pubkey,
const KeyOriginInfo &info);
//! Adds a key to the store, and saves it to disk.
bool AddKeyPubKeyWithDB(WalletBatch &batch, const CKey &key,
const CPubKey &pubkey)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest,
int64_t create_time)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal,
WalletBatch &batch);
//! Adds a script to the store and saves it to disk
bool AddCScriptWithDB(WalletBatch &batch, const CScript &script);
public:
//! Fetches a key from the keypool
bool GetKeyFromPool(CPubKey &key, bool internal = false);
void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Map from Key ID to key metadata.
std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet);
// Map from Script ID to key metadata (for watch-only keys).
std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet);
/**
* keystore implementation
* Generate a new key
*/
CPubKey GenerateNewKey(WalletBatch &batch, bool internal = false)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, and saves it to disk.
bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a key to the store, without saving it to disk (used by LoadWallet)
bool LoadKey(const CKey &key, const CPubKey &pubkey) {
return AddKeyPubKeyInner(key, pubkey);
}
//! Load metadata (used by LoadWallet)
void LoadKeyMetadata(const CKeyID &keyID, const CKeyMetadata &metadata)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void LoadScriptMetadata(const CScriptID &script_id,
const CKeyMetadata &metadata)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Upgrade stored CKeyMetadata objects to store key origin info as
//! KeyOriginInfo
void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void UpdateTimeFirstKey(int64_t nCreateTime)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds an encrypted key to the store, and saves it to disk.
bool AddCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret);
//! Adds an encrypted key to the store, without saving it to disk (used by
//! LoadWallet)
bool LoadCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret);
bool GetKey(const CKeyID &address, CKey &keyOut) const override;
bool GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const override;
bool HaveKey(const CKeyID &address) const override;
std::set<CKeyID> GetKeys() const override;
bool AddCScript(const CScript &redeemScript) override;
bool LoadCScript(const CScript &redeemScript);
//! Adds a watch-only address to the store, and saves it to disk.
bool AddWatchOnly(const CScript &dest, int64_t nCreateTime)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool RemoveWatchOnly(const CScript &dest)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
//! Adds a watch-only address to the store, without saving it to disk (used
//! by LoadWallet)
bool LoadWatchOnly(const CScript &dest);
//! Returns whether the watch-only script is in the wallet
bool HaveWatchOnly(const CScript &dest) const;
//! Returns whether there are any watch-only things in the wallet
bool HaveWatchOnly() const;
//! Fetches a pubkey from mapWatchKeys if it exists there
bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) 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);
bool NewKeyPool();
size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool TopUpKeyPool(unsigned int kpSize = 0);
/**
* Reserves a key from the keypool and sets nIndex to its index
*
* @param[out] nIndex the index of the key in keypool
* @param[out] keypool the keypool the key was drawn from, which could be
* the the pre-split pool if present, or the internal or external pool
* @param fRequestedInternal true if the caller would like the key drawn
* from the internal keypool, false if external is preferred
*
* @return true if succeeded, false if failed due to empty keypool
* @throws std::runtime_error if keypool read failed, key was invalid,
* was not found in the wallet, or was misclassified in the internal
* or external keypool
*/
bool ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool,
bool fRequestedInternal);
void KeepKey(int64_t nIndex);
void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey);
int64_t GetOldestKeyPoolTime();
/**
* Marks all keys in the keypool up to and including reserve_key as used.
*/
void MarkReserveKeysAsUsed(int64_t keypool_id)
EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
const std::map<CKeyID, int64_t> &GetAllReserveKeys() const {
return m_pool_key_to_index;
}
bool GetNewDestination(const OutputType type, const std::string label,
CTxDestination &dest, std::string &error);
isminetype IsMine(const CScript &script) const;
/* Set the HD chain model (chain child index counters) */
void SetHDChain(const CHDChain &chain, bool memonly);
const CHDChain &GetHDChain() const { return hdChain; }
/* Returns true if HD is enabled */
bool IsHDEnabled() const;
/* Returns true if the wallet can generate new keys */
bool CanGenerateKeys();
/* 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);
/* Generates a new HD seed (will not be activated) */
CPubKey GenerateNewSeed();
/* Derives a new HD seed (will not be activated) */
CPubKey DeriveNewSeed(const CKey &key);
/* Set the current HD seed (will reset the chain child index counters)
Sets the seed's version based on the current wallet version (so the
caller must ensure the current wallet version is correct before calling
this function). */
void SetHDSeed(const CPubKey &key);
/**
* Explicitly make the wallet learn the related scripts for outputs to the
* given key. This is purely to make the wallet file compatible with older
* software, as FillableSigningProvider automatically does this implicitly
* for all keys now.
*/
void LearnRelatedScripts(const CPubKey &key, OutputType);
/**
* Same as LearnRelatedScripts, but when the OutputType is not known (and
* could be anything).
*/
void LearnAllRelatedScripts(const CPubKey &key);
/** Implement lookup of key origin information through wallet key metadata.
*/
bool GetKeyOrigin(const CKeyID &keyid, KeyOriginInfo &info) const override;
// Temporary CWallet accessors and aliases.
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;
RecursiveMutex &cs_wallet;
CKeyingMaterial &vMasterKey GUARDED_BY(cs_KeyStore);
std::atomic<bool> &fUseCrypto;
bool &fDecryptionThoroughlyChecked;
};
#endif // BITCOIN_WALLET_SCRIPTPUBKEYMAN_H
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 321ab4fb9..a798748e9 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,4502 +1,4499 @@
// 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/block.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/error.h>
#include <util/moneystr.h>
#include <util/translation.h>
#include <util/validation.h>
#include <wallet/coincontrol.h>
#include <wallet/fees.h>
#include <boost/algorithm/string/replace.hpp>
#include <cassert>
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);
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);
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;
}
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,
std::string &error,
std::vector<std::string> &warnings) {
if (!CWallet::Verify(chainParams, chain, location, false, error,
warnings)) {
error = "Wallet file verification failed: " + error;
return nullptr;
}
std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(
chainParams, chain, location, error, warnings);
if (!wallet) {
error = "Wallet loading failed: " + error;
return nullptr;
}
AddWallet(wallet);
wallet->postInitProcess();
return wallet;
}
std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams,
interfaces::Chain &chain,
const std::string &name, std::string &error,
std::vector<std::string> &warnings) {
return LoadWallet(chainParams, chain, WalletLocation(name), error,
warnings);
}
WalletCreationStatus CreateWallet(const CChainParams &params,
interfaces::Chain &chain,
const SecureString &passphrase,
uint64_t wallet_creation_flags,
const std::string &name, std::string &error,
std::vector<std::string> &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 = "Wallet " + location.GetName() + " already exists.";
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, false, error, warnings)) {
error = "Wallet file verification failed: " + 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 = "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 = "Wallet creation failed: " + error;
return WalletCreationStatus::CREATION_FAILED;
}
// Encrypt the wallet
if (!passphrase.empty() &&
!(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
if (!wallet->EncryptWallet(passphrase)) {
error = "Error: Wallet created but failed to encrypt.";
return WalletCreationStatus::ENCRYPTION_FAILED;
}
if (!create_blank) {
// Unlock the wallet
if (!wallet->Unlock(passphrase)) {
error = "Error: Wallet was encrypted but could not be unlocked";
return WalletCreationStatus::ENCRYPTION_FAILED;
}
// Set a seed for the wallet
CPubKey master_pub_key = wallet->m_spk_man->GenerateNewSeed();
wallet->m_spk_man->SetHDSeed(master_pub_key);
wallet->m_spk_man->NewKeyPool();
// Relock the wallet
wallet->Lock();
}
}
AddWallet(wallet);
wallet->postInitProcess();
result = wallet;
return WalletCreationStatus::SUCCESS;
}
const BlockHash CWalletTx::ABANDON_HASH(uint256S(
"0000000000000000000000000000000000000000000000000000000000000001"));
/** @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));
}
std::vector<CKeyID> GetAffectedKeys(const CScript &spk,
const SigningProvider &provider);
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() {
- AssertLockHeld(m_spk_man->cs_wallet);
if (m_spk_man) {
+ AssertLockHeld(m_spk_man->cs_wallet);
m_spk_man->UpgradeKeyMetadata();
}
}
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(interfaces::Chain::Lock &locked_chain,
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(locked_chain);
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;
assert(!encrypted_batch);
encrypted_batch = new WalletBatch(*database);
if (!encrypted_batch->TxnBegin()) {
delete encrypted_batch;
encrypted_batch = nullptr;
return false;
}
encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
if (auto spk_man = m_spk_man.get()) {
if (!spk_man->EncryptKeys(_vMasterKey)) {
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 HD, replace the HD seed with a new one
if (m_spk_man->IsHDEnabled()) {
m_spk_man->SetHDSeed(m_spk_man->GenerateNewSeed());
}
m_spk_man->NewKeyPool();
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::SetUsedDestinationState(const TxId &hash, unsigned int n,
bool used) {
const CWalletTx *srctx = GetWalletTx(hash);
if (!srctx) {
return;
}
CTxDestination dst;
if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) {
if (IsMine(dst)) {
LOCK(cs_wallet);
if (used && !GetDestData(dst, "used", nullptr)) {
// p for "present", opposite of absent (null)
AddDestData(dst, "used", "p");
} else if (!used && GetDestData(dst, "used", nullptr)) {
EraseDestData(dst, "used");
}
}
}
}
bool CWallet::IsUsedDestination(const CTxDestination &dst) const {
LOCK(cs_wallet);
return IsMine(dst) && GetDestData(dst, "used", nullptr);
}
bool CWallet::IsUsedDestination(const TxId &txid, unsigned int n) const {
CTxDestination dst;
const CWalletTx *srctx = GetWalletTx(txid);
return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) &&
IsUsedDestination(dst);
}
bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) {
LOCK(cs_wallet);
WalletBatch batch(*database, "r+", fFlushOnClose);
const TxId &txid = wtxIn.GetId();
if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// Mark used destinations
for (const CTxIn &txin : wtxIn.tx->vin) {
const COutPoint &op = txin.prevout;
SetUsedDestinationState(op.GetTxId(), op.GetN(), true);
}
}
// 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));
CWalletTx &wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
if (fInsertedNew) {
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;
fUpdated = true;
} else {
assert(wtx.m_confirm.nIndex == wtxIn.m_confirm.nIndex);
assert(wtx.m_confirm.hashBlock == wtxIn.m_confirm.hashBlock);
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) {
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
}
//// debug print
WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(),
(fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if ((fInsertedNew || fUpdated) && !batch.WriteTx(wtx)) {
return false;
}
// 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());
std::thread t(runCommand, strCmd);
// Thread runs free.
t.detach();
}
#endif
return true;
}
void CWallet::LoadToWallet(CWalletTx &wtxIn) {
// If wallet doesn't have a chain (e.g wallet-tool), lock can't be taken.
auto locked_chain = LockChain();
// If tx hasn't been reorged out of chain while wallet being shutdown
// change tx status to UNCONFIRMED and reset hashBlock/nIndex.
if (!wtxIn.m_confirm.hashBlock.IsNull()) {
if (locked_chain &&
!locked_chain->getBlockHeight(wtxIn.m_confirm.hashBlock)) {
wtxIn.setUnconfirmed();
wtxIn.m_confirm.hashBlock = BlockHash();
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, wtx.GetId());
}
}
}
}
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx,
CWalletTx::Status status,
const BlockHash &block_hash,
int posInBlock, bool fUpdate) {
const CTransaction &tx = *ptx;
AssertLockHeld(cs_wallet);
if (!block_hash.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(), block_hash.ToString(),
range.first->second.ToString(),
range.first->first.GetTxId().ToString(),
range.first->first.GetN());
MarkConflicted(block_hash, 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) {
// extract addresses and check if they match with an unused keypool
// key
for (const auto &keyid :
GetAffectedKeys(txout.scriptPubKey, *m_spk_man)) {
std::map<CKeyID, int64_t>::const_iterator mi =
m_spk_man->m_pool_key_to_index.find(keyid);
if (mi != m_spk_man->m_pool_key_to_index.end()) {
WalletLogPrintf("%s: Detected a used keypool key, mark all "
"keypool key up to this key as used\n",
__func__);
MarkReserveKeysAsUsed(mi->second);
if (!m_spk_man->TopUpKeyPool()) {
WalletLogPrintf(
"%s: Topping up keypool failed (locked wallet)\n",
__func__);
}
}
}
}
CWalletTx wtx(this, ptx);
// Block disconnection override an abandoned tx as unconfirmed
// which means user may have to call abandontransaction again
wtx.SetConf(status, block_hash, posInBlock);
return AddToWallet(wtx, false);
}
return false;
}
bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
const CWalletTx *wtx = GetWalletTx(txid);
return wtx && !wtx->isAbandoned() &&
wtx->GetDepthInMainChain(*locked_chain) == 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(interfaces::Chain::Lock &locked_chain,
const TxId &txid) {
// Temporary. Removed in upcoming lock cleanup
auto locked_chain_recursive = chain().lock();
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(locked_chain) != 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(locked_chain);
// 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.m_confirm.nIndex = 0;
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, const TxId &txid) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
int conflictconfirms = -locked_chain->getBlockDepth(hashBlock);
// 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(*locked_chain);
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.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::Status status,
const BlockHash &block_hash, int posInBlock,
bool update_tx) {
if (!AddToWalletIfInvolvingMe(ptx, status, block_hash, posInBlock,
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) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED,
BlockHash() /* block hash */, 0 /* position in block */);
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, const std::vector<CTransactionRef> &vtxConflicted) {
const BlockHash &block_hash = block.GetHash();
auto locked_chain = chain().lock();
LOCK(cs_wallet);
for (size_t i = 0; i < block.vtx.size(); i++) {
SyncTransaction(block.vtx[i], CWalletTx::Status::CONFIRMED, block_hash,
i);
TransactionRemovedFromMempool(block.vtx[i]);
}
for (const CTransactionRef &ptx : vtxConflicted) {
TransactionRemovedFromMempool(ptx);
}
m_last_block_processed = block_hash;
}
void CWallet::BlockDisconnected(const CBlock &block) {
auto locked_chain = chain().lock();
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.
for (const CTransactionRef &ptx : block.vtx) {
SyncTransaction(ptx, CWalletTx::Status::UNCONFIRMED,
BlockHash() /* block hash */,
0 /* position in block */);
}
}
void CWallet::UpdatedBlockTip() {
m_best_block_time = GetTime();
}
void CWallet::BlockUntilSyncedToCurrentChain() {
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().waitForNotificationsIfNewBlocksConnected(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;
if (auto spk_man = m_spk_man.get()) {
result = spk_man->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 (!mapAddressBook.count(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 {
bool result = true;
if (auto spk_man = m_spk_man.get()) {
result &= spk_man->IsHDEnabled();
}
return result;
}
bool CWallet::CanGetAddresses(bool internal) {
{
auto spk_man = m_spk_man.get();
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");
}
}
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;
const SigningProvider *provider = GetSigningProvider();
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;
}
AssertLockHeld(spk_man->cs_wallet);
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;
}
AssertLockHeld(spk_man->cs_wallet);
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;
}
AssertLockHeld(spk_man->cs_wallet);
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;
}
AssertLockHeld(spk_man->cs_wallet);
if (!spk_man->ImportScriptPubKeys(label, script_pub_keys, have_solving_data,
apply_label, timestamp)) {
return false;
}
return true;
}
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 (auto &input : tx.vin) {
const auto mi = wallet->mapWallet.find(input.prevout.GetTxId());
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)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
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)) {
// This should never happen, because IsAllFromMe(ISMINE_SPENDABLE)
// implies that we can sign for every input.
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.
BlockHash start_block;
{
auto locked_chain = chain().lock();
const Optional<int> start_height =
locked_chain->findFirstBlockWithTimeAndHeight(
startTime - TIMESTAMP_WINDOW, 0, &start_block);
const Optional<int> tip_height = locked_chain->getHeight();
WalletLogPrintf(
"%s: Rescanning last %i blocks\n", __func__,
tip_height && start_height ? *tip_height - *start_height + 1 : 0);
}
if (!start_block.IsNull()) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
ScanResult result = ScanForWalletTransactions(start_block, BlockHash(),
reserver, update);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
if (!chain().findBlock(result.last_failed_block,
nullptr /* block */, nullptr /* time */,
&time_max)) {
throw std::logic_error(
"ScanForWalletTransactions returned invalid block hash");
}
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] stop_block Scan ending block. If block is not on the active
* chain, the scan will continue until it reaches the
* chain 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, const BlockHash &stop_block,
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;
// The way the 'block_height' is initialized is just a workaround for
// the gcc bug #47679 since version 4.6.0.
Optional<int> block_height = MakeOptional(false, int());
double progress_begin;
double progress_end;
{
auto locked_chain = chain().lock();
if (Optional<int> tip_height = locked_chain->getHeight()) {
tip_hash = locked_chain->getBlockHash(*tip_height);
}
block_height = locked_chain->getBlockHeight(block_hash);
progress_begin = chain().guessVerificationProgress(block_hash);
progress_end = chain().guessVerificationProgress(
stop_block.IsNull() ? tip_hash : stop_block);
}
double progress_current = progress_begin;
while (block_height && !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;
if (chain().findBlock(block_hash, &block) && !block.IsNull()) {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
if (!locked_chain->getBlockHeight(block_hash)) {
// 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) {
SyncTransaction(block.vtx[posInBlock],
CWalletTx::Status::CONFIRMED, block_hash,
posInBlock, 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;
}
if (block_hash == stop_block) {
break;
}
{
auto locked_chain = chain().lock();
Optional<int> tip_height = locked_chain->getHeight();
if (!tip_height || *tip_height <= block_height ||
!locked_chain->getBlockHeight(block_hash)) {
// 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 = locked_chain->getBlockHash(++*block_height);
progress_current = chain().guessVerificationProgress(block_hash);
// handle updated tip hash
const BlockHash prev_tip_hash = tip_hash;
tip_hash = locked_chain->getBlockHash(*tip_height);
if (stop_block.IsNull() && 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(
interfaces::Chain::Lock &locked_chain) {
// 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(locked_chain);
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, locked_chain);
}
}
bool CWalletTx::SubmitMemoryPoolAndRelay(
std::string &err_string, bool relay,
interfaces::Chain::Lock &locked_chain) {
// 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(locked_chain) != 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, err_string, pwallet->m_default_max_tx_fee, relay);
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));
}
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(interfaces::Chain::Lock &locked_chain,
const isminefilter &filter) const {
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsImmatureCoinBase(locked_chain)) {
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(interfaces::Chain::Lock &locked_chain,
bool fUseCache) const {
if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
}
return Amount::zero();
}
Amount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock &locked_chain,
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(locked_chain)) {
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(locked_chain, COutPoint(txid, i)) &&
(allow_used_addresses || !pwallet->IsUsedDestination(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);
}
return nCredit;
}
Amount
CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock &locked_chain,
const bool fUseCache) const {
if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) {
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(interfaces::Chain::Lock &locked_chain) const {
// Quick answer in most cases
TxValidationState state;
if (!locked_chain.contextualCheckTransactionForCurrentBlock(
Params().GetConsensus(), *tx, state)) {
return false;
}
int nDepth = GetDepthInMainChain(locked_chain);
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()];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) {
return false;
}
}
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);
nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst) {
return;
}
// Only do it if there's been a new block since last time
if (m_best_block_time < nLastResend) {
return;
}
nLastResend = GetTime();
int submitted_tx_count = 0;
{ // locked_chain and cs_wallet scope
auto locked_chain = chain().lock();
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,
*locked_chain)) {
++submitted_tx_count;
}
}
} // locked_chain and 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;
auto locked_chain = chain().lock();
LOCK(cs_wallet);
for (const auto &entry : mapWallet) {
const CWalletTx &wtx = entry.second;
const bool is_trusted{wtx.IsTrusted(*locked_chain)};
const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)};
const Amount tx_credit_mine{
wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true,
ISMINE_SPENDABLE | reuse_filter)};
const Amount tx_credit_watchonly{
wtx.GetAvailableCredit(*locked_chain, /* 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(*locked_chain);
ret.m_watchonly_immature +=
wtx.GetImmatureWatchOnlyCredit(*locked_chain);
}
return ret;
}
Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const {
auto locked_chain = chain().lock();
LOCK(cs_wallet);
Amount balance = Amount::zero();
std::vector<COutput> vCoins;
AvailableCoins(*locked_chain, vCoins, true, coinControl);
for (const COutput &out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
}
}
return balance;
}
void CWallet::AvailableCoins(interfaces::Chain::Lock &locked_chain,
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};
const Consensus::Params params = Params().GetConsensus();
for (const auto &entry : mapWallet) {
const TxId &wtxid = entry.first;
const CWalletTx &wtx = entry.second;
TxValidationState state;
if (!locked_chain.contextualCheckTransactionForCurrentBlock(
params, *wtx.tx, state)) {
continue;
}
if (wtx.IsImmatureCoinBase(locked_chain)) {
continue;
}
int nDepth = wtx.GetDepthInMainChain(locked_chain);
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(locked_chain);
// 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++) {
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(locked_chain, outpoint)) {
continue;
}
isminetype mine = IsMine(wtx.tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
if (!allow_used_addresses && IsUsedDestination(wtxid, i)) {
continue;
}
const SigningProvider *provider = GetSigningProvider();
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(interfaces::Chain::Lock &locked_chain) const {
AssertLockHeld(cs_wallet);
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;
AvailableCoins(locked_chain, availableCoins);
for (const auto &coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
ExtractDestination(
FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey,
address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
for (const auto &output : lockedCoins) {
auto it = mapWallet.find(output.GetTxId());
if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(locked_chain);
if (depth >= 0 && output.GetN() < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.GetN()]) ==
ISMINE_SPENDABLE) {
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);
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);
// 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) {
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) {
// 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<TxId, CWalletTx>::const_iterator it =
mapWallet.find(outpoint.GetTxId());
if (it == mapWallet.end()) {
// TODO: Allow non-wallet inputs
return false;
}
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
nValueFromPresetInputs += wtx.tx->vout[outpoint.GetN()].nValue;
setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.GetN()));
}
// 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;
}
}
// 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);
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);
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));
// 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) {
AssertLockHeld(cs_wallet);
// sign the new tx
int nIn = 0;
for (CTxIn &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 CScript &scriptPubKey =
mi->second.tx->vout[input.prevout.GetN()].scriptPubKey;
const Amount amount = mi->second.tx->vout[input.prevout.GetN()].nValue;
SignatureData sigdata;
SigHashType sigHashType = SigHashType().withForkId();
const SigningProvider *provider = GetSigningProvider();
if (!provider) {
return false;
}
if (!ProduceSignature(*provider,
MutableTransactionSignatureCreator(
&tx, nIn, amount, sigHashType),
scriptPubKey, sigdata)) {
return false;
}
UpdateInput(input, sigdata);
nIn++;
}
return true;
}
bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
int &nChangePosInOut, std::string &strFailReason,
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).
auto locked_chain = chain().lock();
LOCK(cs_wallet);
CTransactionRef tx_new;
if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet,
nChangePosInOut, strFailReason, 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,
interfaces::Chain::Lock &locked_chain) {
if (chain.isInitialBlockDownload()) {
return false;
}
// in seconds
constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60;
if (locked_chain.getBlockTime(*locked_chain.getHeight()) <
(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,
interfaces::Chain::Lock &locked_chain) {
uint32_t const height = locked_chain.getHeight().value_or(-1);
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, locked_chain)) {
locktime = 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 <= height);
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::CreateTransaction(interfaces::Chain::Lock &locked_chainIn,
const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, Amount &nFeeRet,
int &nChangePosInOut,
std::string &strFailReason,
const CCoinControl &coinControl, bool sign) {
Amount nValue = Amount::zero();
ReserveDestination reservedest(this);
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto &recipient : vecSend) {
if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) {
strFailReason =
_("Transaction amounts must not be negative").translated;
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount) {
nSubtractFeeFromAmount++;
}
}
if (vecSend.empty()) {
strFailReason =
_("Transaction must have at least one recipient").translated;
return false;
}
CMutableTransaction txNew;
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), locked_chainIn);
{
std::set<CInputCoin> setCoins;
auto locked_chain = chain().lock();
LOCK(cs_wallet);
std::vector<COutput> vAvailableCoins;
AvailableCoins(*locked_chain, vAvailableCoins, true, &coinControl);
// 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>(&coinControl.destChange)) {
scriptChange = GetScriptForDestination(coinControl.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 (!CanGetAddresses(true)) {
strFailReason =
_("Can't generate a change-address key. No keys in the "
"internal keypool and can't generate any keys.")
.translated;
return false;
}
CTxDestination dest;
const OutputType change_type = TransactionChangeType(
coinControl.m_change_type ? *coinControl.m_change_type
: m_default_change_type,
vecSend);
bool ret =
reservedest.GetReservedDestination(change_type, dest, true);
if (!ret) {
strFailReason =
_("Keypool ran out, please call keypoolrefill first")
.translated;
return false;
}
scriptChange = GetScriptForDestination(dest);
}
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, coinControl);
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.
// If we are doing subtract fee from recipient, then don't use BnB
coin_selection_params.use_bnb = 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;
}
// 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
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()) {
strFailReason = _("The transaction amount is too "
"small to pay the fee")
.translated;
} else {
strFailReason =
_("The transaction amount is too small to "
"send after the fee has been deducted")
.translated;
}
} else {
strFailReason =
_("Transaction amount too small").translated;
}
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use
bool bnb_used;
if (pick_new_inputs) {
nValueIn = Amount::zero();
setCoins.clear();
coin_selection_params.change_spend_size =
CalculateMaximumSignedInputSize(change_prototype_txout,
this);
coin_selection_params.effective_fee = nFeeRateNeeded;
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins,
nValueIn, coinControl, 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 {
strFailReason = _("Insufficient funds").translated;
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()) {
strFailReason =
_("Change index out of range").translated;
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, coinControl.fAllowWatchOnly);
if (nBytes < 0) {
strFailReason = _("Signing transaction failed").translated;
return false;
}
Amount nFeeNeeded = GetMinimumFee(*this, nBytes, coinControl);
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, coinControl);
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.
strFailReason =
_("Transaction fee and change calculation failed")
.translated;
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;
}
// 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) {
SigHashType sigHashType = SigHashType().withForkId();
int nIn = 0;
for (const auto &coin : selected_coins) {
const CScript &scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
const SigningProvider *provider = GetSigningProvider();
- if (!provider) {
- return false;
- }
-
- if (!ProduceSignature(
+ if (!provider ||
+ !ProduceSignature(
*provider,
MutableTransactionSignatureCreator(
&txNew, nIn, coin.txout.nValue, sigHashType),
scriptPubKey, sigdata)) {
strFailReason = _("Signing transaction failed").translated;
return false;
}
UpdateInput(txNew.vin.at(nIn), sigdata);
nIn++;
}
}
// Return the constructed transaction data.
tx = MakeTransactionRef(std::move(txNew));
// Limit size.
if (tx->GetTotalSize() > MAX_STANDARD_TX_SIZE) {
strFailReason = _("Transaction too large").translated;
return false;
}
}
if (nFeeRet > m_default_max_tx_fee) {
strFailReason =
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)) {
strFailReason =
_("Transaction has too long of a mempool chain").translated;
return false;
}
}
// Before we return success, we assume any change key will be used to
// prevent accidental re-use.
reservedest.KeepDestination();
return true;
}
void CWallet::CommitTransaction(
CTransactionRef tx, mapValue_t mapValue,
std::vector<std::pair<std::string, std::string>> orderForm) {
auto locked_chain = chain().lock();
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());
// Add tx to wallet, because if it has change it's also ours, otherwise just
// for transaction history.
AddToWallet(wtxNew);
// Notify that old coins are spent.
for (const CTxIn &txin : wtxNew.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());
if (!fBroadcastTransactions) {
// Don't submit tx to the mempool
return;
}
std::string err_string;
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true, *locked_chain)) {
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) {
// 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
// status may be not reliable.
auto locked_chain = LockChain();
LOCK(cs_wallet);
fFirstRunRet = false;
DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
if (database->Rewrite("\x04pool")) {
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_spk_man->m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
{
LOCK(cs_KeyStore);
// This wallet is in its first run if all of these are empty
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() &&
mapWatchKeys.empty() && setWatchOnly.empty() &&
mapScripts.empty() &&
!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) &&
!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET);
}
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")) {
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_spk_man->m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
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")) {
LOCK(cs_wallet);
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_spk_man->m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
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 =
mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
// Update purpose only if requested.
if (!strPurpose.empty()) {
mapAddressBook[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) {
{
LOCK(cs_wallet);
// Delete destdata tuples associated with address.
for (const std::pair<const std::string, std::string> &item :
mapAddressBook[address].destdata) {
WalletBatch(*database).EraseDestData(address, item.first);
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "", IsMine(address) != ISMINE_NO,
"", CT_DELETED);
WalletBatch(*database).ErasePurpose(address);
return WalletBatch(*database).EraseName(address);
}
size_t CWallet::KeypoolCountExternalKeys() {
AssertLockHeld(cs_wallet);
unsigned int count = 0;
if (auto spk_man = m_spk_man.get()) {
AssertLockHeld(spk_man->cs_wallet);
count += spk_man->KeypoolCountExternalKeys();
}
return count;
}
bool CWallet::TopUpKeyPool(unsigned int kpSize) {
bool res = true;
if (auto spk_man = m_spk_man.get()) {
res &= spk_man->TopUpKeyPool(kpSize);
}
return res;
}
bool CWallet::GetNewDestination(const OutputType type, const std::string label,
CTxDestination &dest, std::string &error) {
error.clear();
bool result = false;
auto spk_man = m_spk_man.get();
if (spk_man) {
result = spk_man->GetNewDestination(type, label, dest, error);
}
return result;
}
bool CWallet::GetNewChangeDestination(const OutputType type,
CTxDestination &dest,
std::string &error) {
error.clear();
m_spk_man->TopUpKeyPool();
ReserveDestination reservedest(this);
if (!reservedest.GetReservedDestination(type, dest, true)) {
error = "Error: Keypool ran out, please call keypoolrefill first";
return false;
}
reservedest.KeepDestination();
return true;
}
int64_t CWallet::GetOldestKeyPoolTime() {
int64_t oldestKey = std::numeric_limits<int64_t>::max();
if (auto spk_man = m_spk_man.get()) {
oldestKey = spk_man->GetOldestKeyPoolTime();
}
return oldestKey;
}
std::map<CTxDestination, Amount>
CWallet::GetAddressBalances(interfaces::Chain::Lock &locked_chain) {
std::map<CTxDestination, Amount> balances;
LOCK(cs_wallet);
for (const auto &walletEntry : mapWallet) {
const CWalletTx &wtx = walletEntry.second;
if (!wtx.IsTrusted(locked_chain)) {
continue;
}
if (wtx.IsImmatureCoinBase(locked_chain)) {
continue;
}
int nDepth = wtx.GetDepthInMainChain(locked_chain);
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(locked_chain, 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() {
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 :
mapAddressBook) {
const CTxDestination &address = item.first;
const std::string &strName = item.second.name;
if (strName == label) {
result.insert(address);
}
}
return result;
}
bool ReserveDestination::GetReservedDestination(const OutputType type,
CTxDestination &dest,
bool internal) {
m_spk_man = pwallet->GetLegacyScriptPubKeyMan();
if (!m_spk_man) {
return false;
}
if (!pwallet->CanGetAddresses(internal)) {
return false;
}
if (nIndex == -1) {
CKeyPool keypool;
if (!m_spk_man->ReserveKeyFromKeyPool(nIndex, keypool, internal)) {
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->KeepKey(nIndex);
}
nIndex = -1;
vchPubKey = CPubKey();
address = CNoDestination();
}
void ReserveDestination::ReturnDestination() {
if (nIndex != -1) {
m_spk_man->ReturnKey(nIndex, fInternal, vchPubKey);
}
nIndex = -1;
vchPubKey = CPubKey();
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(interfaces::Chain::Lock &locked_chain,
std::map<CKeyID, int64_t> &mapKeyBirth) const {
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
LegacyScriptPubKeyMan *spk_man = GetLegacyScriptPubKeyMan();
assert(spk_man != nullptr);
AssertLockHeld(spk_man->cs_wallet);
// 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
const Optional<int> tip_height = locked_chain.getHeight();
// the tip can be reorganized; use a 144-block safety margin
const int max_height =
tip_height && *tip_height > 144 ? *tip_height - 144 : 0;
std::map<CKeyID, int> mapKeyFirstBlock;
for (const CKeyID &keyid : spk_man->GetKeys()) {
if (mapKeyBirth.count(keyid) == 0) {
mapKeyFirstBlock[keyid] = max_height;
}
}
// 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 (Optional<int> height =
locked_chain.getBlockHeight(wtx.m_confirm.hashBlock)) {
// ... 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.
std::map<CKeyID, int>::iterator rit =
mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() &&
*height < rit->second) {
rit->second = *height;
}
}
}
}
}
// Extract block timestamps for those keys.
for (const auto &entry : mapKeyFirstBlock) {
// block times can be 2h off
mapKeyBirth[entry.first] =
locked_chain.getBlockTime(entry.second) - 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, nullptr /* block */,
&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(const CTxDestination &dest, const std::string &key,
const std::string &value) {
if (boost::get<CNoDestination>(&dest)) {
return false;
}
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return WalletBatch(*database).WriteDestData(dest, key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest,
const std::string &key) {
if (!mapAddressBook[dest].destdata.erase(key)) {
return false;
}
return WalletBatch(*database).EraseDestData(dest, key);
}
void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
mapAddressBook[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 =
mapAddressBook.find(dest);
if (i != mapAddressBook.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 : mapAddressBook) {
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, bool salvage_wallet,
std::string &error_string,
std::vector<std::string> &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 =
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 = 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 {
if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) {
return false;
}
} catch (const fs::filesystem_error &e) {
error_string =
strprintf("Error loading wallet %s. %s", location.GetName(),
fsbridge::get_filesystem_error_message(e));
return false;
}
if (salvage_wallet) {
// Recover readable keypairs:
CWallet dummyWallet(chainParams, &chain, WalletLocation(),
WalletDatabase::CreateDummy());
std::string backup_filename;
// 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, tx confirmation status may be not
// reliable.
auto locked_chain = dummyWallet.LockChain();
if (!WalletBatch::Recover(
wallet_path, static_cast<void *>(&dummyWallet),
WalletBatch::RecoverKeysOnlyFilter, backup_filename)) {
return false;
}
}
return WalletBatch::VerifyDatabaseFile(wallet_path, warnings, error_string);
}
std::shared_ptr<CWallet> CWallet::CreateWalletFromFile(
const CChainParams &chainParams, interfaces::Chain &chain,
const WalletLocation &location, std::string &error,
std::vector<std::string> &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>(
chainParams, &chain, location,
WalletDatabase::Create(location.GetPath()));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DBErrors::LOAD_OK) {
error = strprintf(
_("Error loading %s: Wallet corrupted").translated, 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(chainParams, &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").translated, 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.")
.translated,
walletFile));
} else if (nLoadWalletRet == DBErrors::TOO_NEW) {
error = strprintf(
_("Error loading %s: Wallet requires newer version of %s")
.translated,
walletFile, PACKAGE_NAME);
return nullptr;
} else if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
error = strprintf(
_("Wallet needed to be rewritten: restart %s to complete")
.translated,
PACKAGE_NAME);
return nullptr;
} else {
error = strprintf(_("Error loading %s").translated, walletFile);
return nullptr;
}
}
int prev_version = walletInstance->GetVersion();
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) {
int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
// The -upgradewallet without argument case
if (nMaxVersion == 0) {
walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n",
FEATURE_LATEST);
nMaxVersion = FEATURE_LATEST;
// permanently upgrade the wallet immediately
walletInstance->SetMinVersion(FEATURE_LATEST);
} else {
walletInstance->WalletLogPrintf(
"Allowing wallet upgrade up to %i\n", nMaxVersion);
}
if (nMaxVersion < walletInstance->GetVersion()) {
error = _("Cannot downgrade wallet").translated;
return nullptr;
}
walletInstance->SetMaxVersion(nMaxVersion);
}
// Upgrade to HD if explicit upgrade
if (gArgs.GetBoolArg("-upgradewallet", false)) {
LOCK(walletInstance->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 = walletInstance->GetVersion();
if (!walletInstance->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 -upgradewallet=200300 "
"or -upgradewallet with no version specified.")
.translated;
return nullptr;
}
bool hd_upgrade = false;
bool split_upgrade = false;
if (walletInstance->CanSupportFeature(FEATURE_HD) &&
!walletInstance->m_spk_man->IsHDEnabled()) {
walletInstance->WalletLogPrintf("Upgrading wallet to HD\n");
walletInstance->SetMinVersion(FEATURE_HD);
// generate a new master key
CPubKey masterPubKey = walletInstance->m_spk_man->GenerateNewSeed();
walletInstance->m_spk_man->SetHDSeed(masterPubKey);
hd_upgrade = true;
}
// Upgrade to HD chain split if necessary
if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) {
walletInstance->WalletLogPrintf(
"Upgrading wallet to use HD chain split\n");
walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL);
split_upgrade = FEATURE_HD_SPLIT > prev_version;
}
// Mark all keys currently in the keypool as pre-split
if (split_upgrade) {
walletInstance->MarkPreSplitKeys();
}
// Regenerate the keypool if upgraded to HD
if (hd_upgrade) {
if (!walletInstance->m_spk_man->TopUpKeyPool()) {
error = _("Unable to generate keys").translated;
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);
if (!(wallet_creation_flags &
(WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) {
// generate a new seed
CPubKey seed = walletInstance->m_spk_man->GenerateNewSeed();
walletInstance->m_spk_man->SetHDSeed(seed);
}
// Top up the keypool
if (walletInstance->m_spk_man->CanGenerateKeys() &&
!walletInstance->m_spk_man->TopUpKeyPool()) {
error = _("Unable to generate initial keys").translated;
return nullptr;
}
auto locked_chain = chain.lock();
walletInstance->ChainStateFlushed(locked_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")
.translated,
walletFile);
return nullptr;
} else if (walletInstance->IsWalletFlagSet(
WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
LOCK(walletInstance->cs_KeyStore);
if (!walletInstance->mapKeys.empty() ||
!walletInstance->mapCryptedKeys.empty()) {
warnings.push_back(
strprintf(_("Warning: Private keys detected in wallet {%s} "
"with disabled private keys")
.translated,
walletFile));
}
}
if (gArgs.IsArgSet("-mintxfee")) {
Amount n = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) ||
n == Amount::zero()) {
error = AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))
.translated;
return nullptr;
}
if (n > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-mintxfee").translated + " " +
_("This is the minimum transaction fee you pay "
"on every transaction.")
.translated);
}
walletInstance->m_min_fee = CFeeRate(n);
}
if (gArgs.IsArgSet("-fallbackfee")) {
Amount nFeePerK = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) {
error = strprintf(
_("Invalid amount for -fallbackfee=<amount>: '%s'").translated,
gArgs.GetArg("-fallbackfee", ""));
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-fallbackfee").translated + " " +
_("This is the transaction fee you may pay when "
"fee estimates are not available.")
.translated);
}
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", ""))
.translated;
return nullptr;
}
if (nFeePerK > HIGH_TX_FEE_PER_KB) {
warnings.push_back(AmountHighWarn("-paytxfee").translated + " " +
_("This is the transaction fee you will pay if "
"you send a transaction.")
.translated);
}
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)")
.translated,
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", ""))
.translated;
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.")
.translated);
}
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)")
.translated,
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").translated + " " +
_("The wallet will avoid paying less than the minimum relay fee.")
.translated);
}
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();
auto locked_chain = chain.lock();
LOCK(walletInstance->cs_wallet);
int rescan_height = 0;
if (!gArgs.GetBoolArg("-rescan", false)) {
WalletBatch batch(*walletInstance->database);
CBlockLocator locator;
if (batch.ReadBestBlock(locator)) {
if (const Optional<int> fork_height =
locked_chain->findLocatorFork(locator)) {
rescan_height = *fork_height;
}
}
}
const Optional<int> tip_height = locked_chain->getHeight();
if (tip_height) {
walletInstance->m_last_block_processed =
locked_chain->getBlockHash(*tip_height);
} else {
walletInstance->m_last_block_processed.SetNull();
}
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 &&
locked_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)")
.translated;
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)
if (walletInstance->nTimeFirstKey) {
if (Optional<int> first_block =
locked_chain->findFirstBlockWithTimeAndHeight(
walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW,
rescan_height, nullptr)) {
rescan_height = *first_block;
}
}
{
WalletRescanReserver reserver(walletInstance.get());
if (!reserver.reserve() ||
(ScanResult::SUCCESS !=
walletInstance
->ScanForWalletTransactions(
locked_chain->getBlockHash(rescan_height), BlockHash(),
reserver, true /* update */)
.status)) {
error = _("Failed to rescan the wallet during initialization")
.translated;
return nullptr;
}
}
walletInstance->ChainStateFlushed(locked_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);
}
}
}
}
chain.loadWallet(interfaces::MakeWallet(walletInstance));
// Register with the validation interface. It's ok to do this after rescan
// since we're still holding locked_chain.
walletInstance->m_chain_notifications_handler =
walletInstance->chain().handleNotifications(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("mapAddressBook.size() = %u\n",
walletInstance->mapAddressBook.size());
return walletInstance;
}
void CWallet::postInitProcess() {
auto locked_chain = chain().lock();
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(*locked_chain);
// Update wallet transactions with current mempool transactions.
chain().requestMempoolTransactions(*this);
}
bool CWallet::BackupWallet(const std::string &strDest) {
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;
}
void CWalletTx::SetConf(Status status, const BlockHash &block_hash,
int posInBlock) {
// Update tx status
m_confirm.status = status;
// Update the tx's hashBlock
m_confirm.hashBlock = block_hash;
// Set the position of the transaction in the block.
m_confirm.nIndex = posInBlock;
}
int CWalletTx::GetDepthInMainChain(
interfaces::Chain::Lock &locked_chain) const {
if (isUnconfirmed() || isAbandoned()) {
return 0;
}
return locked_chain.getBlockDepth(m_confirm.hashBlock) *
(isConflicted() ? -1 : 1);
}
int CWalletTx::GetBlocksToMaturity(
interfaces::Chain::Lock &locked_chain) const {
if (!IsCoinBase()) {
return 0;
}
int chain_depth = GetDepthInMainChain(locked_chain);
// coinbase tx should not be conflicted
assert(chain_depth >= 0);
return std::max(0, (COINBASE_MATURITY + 1) - chain_depth);
}
bool CWalletTx::IsImmatureCoinBase(
interfaces::Chain::Lock &locked_chain) const {
// note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity(locked_chain) > 0;
}
std::vector<OutputGroup>
CWallet::GroupOutputs(const std::vector<COutput> &outputs,
bool single_coin) const {
std::vector<OutputGroup> groups;
std::map<CTxDestination, OutputGroup> gmap;
CTxDestination dst;
for (const auto &output : outputs) {
if (output.fSpendable) {
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)) {
// Limit output groups to no more than 10 entries, to protect
// against inadvertently creating a too-large transaction
// when using -avoidpartialspends
if (gmap[dst].m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) {
groups.push_back(gmap[dst]);
gmap.erase(dst);
}
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 (const auto &it : gmap) {
groups.push_back(it.second);
}
}
return groups;
}
bool CWallet::SetCrypted() {
LOCK(cs_KeyStore);
if (fUseCrypto) {
return true;
}
if (!mapKeys.empty()) {
return false;
}
fUseCrypto = true;
return true;
}
bool CWallet::IsLocked() const {
if (!IsCrypted()) {
return false;
}
LOCK(cs_KeyStore);
return vMasterKey.empty();
}
bool CWallet::Lock() {
if (!SetCrypted()) {
return false;
}
{
LOCK(cs_KeyStore);
vMasterKey.clear();
}
NotifyStatusChanged(this);
return true;
}
ScriptPubKeyMan *CWallet::GetScriptPubKeyMan() const {
return m_spk_man.get();
}
const SigningProvider *CWallet::GetSigningProvider() const {
return m_spk_man.get();
}
LegacyScriptPubKeyMan *CWallet::GetLegacyScriptPubKeyMan() const {
return m_spk_man.get();
}

File Metadata

Mime Type
text/x-diff
Expires
Sun, Mar 2, 11:17 (1 d, 16 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5187561
Default Alt Text
(303 KB)

Event Timeline