aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/bdb.cpp1
-rw-r--r--src/wallet/feebumper.cpp13
-rw-r--r--src/wallet/interfaces.cpp52
-rw-r--r--src/wallet/receive.cpp16
-rw-r--r--src/wallet/rpc/addresses.cpp51
-rw-r--r--src/wallet/rpc/backup.cpp20
-rw-r--r--src/wallet/rpc/coins.cpp6
-rw-r--r--src/wallet/rpc/spend.cpp14
-rw-r--r--src/wallet/rpc/transactions.cpp81
-rw-r--r--src/wallet/scriptpubkeyman.cpp35
-rw-r--r--src/wallet/scriptpubkeyman.h7
-rw-r--r--src/wallet/spend.cpp138
-rw-r--r--src/wallet/spend.h26
-rw-r--r--src/wallet/test/coinselector_tests.cpp29
-rw-r--r--src/wallet/test/fuzz/notifications.cpp29
-rw-r--r--src/wallet/test/spend_tests.cpp15
-rw-r--r--src/wallet/test/util.cpp2
-rw-r--r--src/wallet/test/wallet_tests.cpp51
-rw-r--r--src/wallet/wallet.cpp167
-rw-r--r--src/wallet/wallet.h46
20 files changed, 427 insertions, 372 deletions
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index dbd768a758..60715ff3c8 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -3,6 +3,7 @@
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+#include <compat/compat.h>
#include <fs.h>
#include <wallet/bdb.h>
#include <wallet/db.h>
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 5e70ed4a30..43fdcee48d 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -219,19 +219,18 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
new_coin_control.m_min_depth = 1;
constexpr int RANDOM_CHANGE_POSITION = -1;
- bilingual_str fail_reason;
- FeeCalculation fee_calc_out;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, fail_reason, new_coin_control, fee_calc_out, false);
- if (!txr) {
- errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
+ auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, new_coin_control, false);
+ if (!res) {
+ errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + res.GetError());
return Result::WALLET_ERROR;
}
+ const auto& txr = res.GetObj();
// Write back new fee if successful
- new_fee = txr->fee;
+ new_fee = txr.fee;
// Write back transaction
- mtx = CMutableTransaction(*txr->tx);
+ mtx = CMutableTransaction(*txr.tx);
return Result::OK;
}
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index f54b2c83d2..23f91d9b3a 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -146,11 +146,10 @@ public:
void abortRescan() override { m_wallet->AbortRescan(); }
bool backupWallet(const std::string& filename) override { return m_wallet->BackupWallet(filename); }
std::string getWalletName() override { return m_wallet->GetName(); }
- bool getNewDestination(const OutputType type, const std::string label, CTxDestination& dest) override
+ BResult<CTxDestination> getNewDestination(const OutputType type, const std::string label) override
{
LOCK(m_wallet->cs_wallet);
- bilingual_str error;
- return m_wallet->GetNewDestination(type, label, dest, error);
+ return m_wallet->GetNewDestination(type, label);
}
bool getPubKey(const CScript& script, const CKeyID& address, CPubKey& pub_key) override
{
@@ -191,29 +190,27 @@ public:
std::string* purpose) override
{
LOCK(m_wallet->cs_wallet);
- auto it = m_wallet->m_address_book.find(dest);
- if (it == m_wallet->m_address_book.end() || it->second.IsChange()) {
- return false;
- }
+ const auto& entry = m_wallet->FindAddressBookEntry(dest, /*allow_change=*/false);
+ if (!entry) return false; // addr not found
if (name) {
- *name = it->second.GetLabel();
+ *name = entry->GetLabel();
}
if (is_mine) {
*is_mine = m_wallet->IsMine(dest);
}
if (purpose) {
- *purpose = it->second.purpose;
+ *purpose = entry->purpose;
}
return true;
}
- std::vector<WalletAddress> getAddresses() override
+ std::vector<WalletAddress> getAddresses() const override
{
LOCK(m_wallet->cs_wallet);
std::vector<WalletAddress> result;
- for (const auto& item : m_wallet->m_address_book) {
- if (item.second.IsChange()) continue;
- result.emplace_back(item.first, m_wallet->IsMine(item.first), item.second.GetLabel(), item.second.purpose);
- }
+ m_wallet->ForEachAddrBookEntry([&](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) EXCLUSIVE_LOCKS_REQUIRED(m_wallet->cs_wallet) {
+ if (is_change) return;
+ result.emplace_back(dest, m_wallet->IsMine(dest), label, purpose);
+ });
return result;
}
std::vector<std::string> getAddressReceiveRequests() override {
@@ -252,22 +249,21 @@ public:
LOCK(m_wallet->cs_wallet);
return m_wallet->ListLockedCoins(outputs);
}
- CTransactionRef createTransaction(const std::vector<CRecipient>& recipients,
+ BResult<CTransactionRef> createTransaction(const std::vector<CRecipient>& recipients,
const CCoinControl& coin_control,
bool sign,
int& change_pos,
- CAmount& fee,
- bilingual_str& fail_reason) override
+ CAmount& fee) override
{
LOCK(m_wallet->cs_wallet);
- FeeCalculation fee_calc_out;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(*m_wallet, recipients, change_pos,
- fail_reason, coin_control, fee_calc_out, sign);
- if (!txr) return {};
- fee = txr->fee;
- change_pos = txr->change_pos;
+ const auto& res = CreateTransaction(*m_wallet, recipients, change_pos,
+ coin_control, sign);
+ if (!res) return res.GetError();
+ const auto& txr = res.GetObj();
+ fee = txr.fee;
+ change_pos = txr.change_pos;
- return txr->tx;
+ return txr.tx;
}
void commitTransaction(CTransactionRef tx,
WalletValueMap value_map,
@@ -573,11 +569,13 @@ public:
options.require_existing = true;
return MakeWallet(m_context, LoadWallet(m_context, name, true /* load_on_start */, options, status, error, warnings));
}
- std::unique_ptr<Wallet> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, bilingual_str& error, std::vector<bilingual_str>& warnings) override
+ BResult<std::unique_ptr<Wallet>> restoreWallet(const fs::path& backup_file, const std::string& wallet_name, std::vector<bilingual_str>& warnings) override
{
DatabaseStatus status;
-
- return MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings));
+ bilingual_str error;
+ BResult<std::unique_ptr<Wallet>> wallet{MakeWallet(m_context, RestoreWallet(m_context, backup_file, wallet_name, /*load_on_start=*/true, status, error, warnings))};
+ if (!wallet) return error;
+ return wallet;
}
std::string getWalletDir() override
{
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index e0b2264b13..944925d600 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -115,12 +115,10 @@ CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const ism
return 0;
CAmount credit = 0;
- if (filter & ISMINE_SPENDABLE) {
+ const isminefilter get_amount_filter{filter & ISMINE_ALL};
+ if (get_amount_filter) {
// GetBalance can assume transactions in mapWallet won't change
- credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE);
- }
- if (filter & ISMINE_WATCH_ONLY) {
- credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY);
+ credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, get_amount_filter);
}
return credit;
}
@@ -131,11 +129,9 @@ CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const ismi
return 0;
CAmount debit = 0;
- if (filter & ISMINE_SPENDABLE) {
- debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE);
- }
- if (filter & ISMINE_WATCH_ONLY) {
- debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY);
+ const isminefilter get_amount_filter{filter & ISMINE_ALL};
+ if (get_amount_filter) {
+ debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, get_amount_filter);
}
return debit;
}
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index f25ad59528..9428d049de 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -58,13 +58,12 @@ RPCHelpMan getnewaddress()
output_type = parsed.value();
}
- CTxDestination dest;
- bilingual_str error;
- if (!pwallet->GetNewDestination(output_type, label, dest, error)) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
+ auto op_dest = pwallet->GetNewDestination(output_type, label);
+ if (!op_dest) {
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
}
- return EncodeDestination(dest);
+ return EncodeDestination(op_dest.GetObj());
},
};
}
@@ -106,12 +105,11 @@ RPCHelpMan getrawchangeaddress()
output_type = parsed.value();
}
- CTxDestination dest;
- bilingual_str error;
- if (!pwallet->GetNewChangeDestination(output_type, dest, error)) {
- throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error.original);
+ auto op_dest = pwallet->GetNewChangeDestination(output_type);
+ if (!op_dest) {
+ throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, op_dest.GetError().original);
}
- return EncodeDestination(dest);
+ return EncodeDestination(op_dest.GetObj());
},
};
}
@@ -637,17 +635,6 @@ RPCHelpMan getaddressinfo()
};
}
-/** Convert CAddressBookData to JSON record. */
-static UniValue AddressBookDataToJSON(const CAddressBookData& data, const bool verbose)
-{
- UniValue ret(UniValue::VOBJ);
- if (verbose) {
- ret.pushKV("name", data.GetLabel());
- }
- ret.pushKV("purpose", data.purpose);
- return ret;
-}
-
RPCHelpMan getaddressesbylabel()
{
return RPCHelpMan{"getaddressesbylabel",
@@ -680,10 +667,10 @@ RPCHelpMan getaddressesbylabel()
// Find all addresses that have the given label
UniValue ret(UniValue::VOBJ);
std::set<std::string> addresses;
- for (const std::pair<const CTxDestination, CAddressBookData>& item : pwallet->m_address_book) {
- if (item.second.IsChange()) continue;
- if (item.second.GetLabel() == label) {
- std::string address = EncodeDestination(item.first);
+ pwallet->ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label, const std::string& _purpose, bool _is_change) {
+ if (_is_change) return;
+ if (_label == label) {
+ std::string address = EncodeDestination(_dest);
// CWallet::m_address_book is not expected to contain duplicate
// address strings, but build a separate set as a precaution just in
// case it does.
@@ -693,9 +680,11 @@ RPCHelpMan getaddressesbylabel()
// and since duplicate addresses are unexpected (checked with
// std::set in O(log(N))), UniValue::__pushKV is used instead,
// which currently is O(1).
- ret.__pushKV(address, AddressBookDataToJSON(item.second, false));
+ UniValue value(UniValue::VOBJ);
+ value.pushKV("purpose", _purpose);
+ ret.__pushKV(address, value);
}
- }
+ });
if (ret.empty()) {
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label));
@@ -742,13 +731,7 @@ RPCHelpMan listlabels()
}
// Add to a set to sort by label name, then insert into Univalue array
- std::set<std::string> label_set;
- for (const std::pair<const CTxDestination, CAddressBookData>& entry : pwallet->m_address_book) {
- if (entry.second.IsChange()) continue;
- if (purpose.empty() || entry.second.purpose == purpose) {
- label_set.insert(entry.second.GetLabel());
- }
- }
+ std::set<std::string> label_set = pwallet->ListAddrBookLabels(purpose);
UniValue ret(UniValue::VARR);
for (const std::string& name : label_set) {
diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
index 5f1673fb12..62aba4f3f2 100644
--- a/src/wallet/rpc/backup.cpp
+++ b/src/wallet/rpc/backup.cpp
@@ -100,11 +100,13 @@ RPCHelpMan importprivkey()
"Hint: use importmulti to import more than one private key.\n"
"\nNote: This call can take over an hour 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"
+ "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
+ "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"},
{"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"},
- {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
+ {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
@@ -201,6 +203,8 @@ 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 over an hour 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"
+ "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
+ "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
"If you have the full public key, you should call importpubkey instead of this.\n"
"Hint: use importmulti to import more than one address.\n"
"\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
@@ -210,7 +214,7 @@ RPCHelpMan importaddress()
{
{"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Bitcoin address (or hex-encoded script)"},
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
- {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
+ {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
{"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"},
},
RPCResult{RPCResult::Type::NONE, "", ""},
@@ -401,11 +405,13 @@ RPCHelpMan importpubkey()
"Hint: use importmulti to import more than one public key.\n"
"\nNote: This call can take over an hour 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"
+ "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
+ "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"},
{"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
- {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Rescan the wallet for transactions"},
+ {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
},
RPCResult{RPCResult::Type::NONE, "", ""},
RPCExamples{
@@ -484,7 +490,7 @@ RPCHelpMan importwallet()
{
return RPCHelpMan{"importwallet",
"\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
- "Note: Use \"getwalletinfo\" to query the scanning progress.\n",
+ "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"},
},
@@ -1250,6 +1256,8 @@ RPCHelpMan importmulti()
"Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n"
"\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
"may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
+ "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
+ "but the key was used to create transactions, rescanwallet needs to be called with the appropriate block range.\n"
"Note: Use \"getwalletinfo\" to query the scanning progress.\n",
{
{"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
@@ -1291,7 +1299,7 @@ RPCHelpMan importmulti()
"\"requests\""},
{"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "",
{
- {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Stating if should rescan the blockchain after all imports"},
+ {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."},
},
"\"options\""},
},
@@ -1593,7 +1601,7 @@ RPCHelpMan importdescriptors()
" Use the string \"now\" to substitute the current synced blockchain time.\n"
" \"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
" 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
- " of all descriptors being imported will be scanned.",
+ "of all descriptors being imported will be scanned as well as the mempool.",
/*oneline_description=*/"", {"timestamp | \"now\"", "integer / string"}
},
{"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
diff --git a/src/wallet/rpc/coins.cpp b/src/wallet/rpc/coins.cpp
index ad59cc94ff..a9fff95882 100644
--- a/src/wallet/rpc/coins.cpp
+++ b/src/wallet/rpc/coins.cpp
@@ -18,10 +18,10 @@
namespace wallet {
static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
- std::set<CTxDestination> addresses;
+ std::vector<CTxDestination> addresses;
if (by_label) {
// Get the set of addresses assigned to label
- addresses = wallet.GetLabelAddresses(LabelFromValue(params[0]));
+ addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
if (addresses.empty()) throw JSONRPCError(RPC_WALLET_ERROR, "Label not found in wallet");
} else {
// Get the address
@@ -29,7 +29,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
if (!IsValidDestination(dest)) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
}
- addresses.insert(dest);
+ addresses.emplace_back(dest);
}
// Filter by own scripts only
diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
index 0e8cee5db8..83e23497cb 100644
--- a/src/wallet/rpc/spend.cpp
+++ b/src/wallet/rpc/spend.cpp
@@ -156,18 +156,16 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
// Send
constexpr int RANDOM_CHANGE_POSITION = -1;
- bilingual_str error;
- FeeCalculation fee_calc_out;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc_out, true);
- if (!txr) {
- throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
+ auto res = CreateTransaction(wallet, recipients, RANDOM_CHANGE_POSITION, coin_control, true);
+ if (!res) {
+ throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, res.GetError().original);
}
- CTransactionRef tx = txr->tx;
+ const CTransactionRef& tx = res.GetObj().tx;
wallet.CommitTransaction(tx, std::move(map_value), {} /* orderForm */);
if (verbose) {
UniValue entry(UniValue::VOBJ);
entry.pushKV("txid", tx->GetHash().GetHex());
- entry.pushKV("fee_reason", StringForFeeReason(fee_calc_out.reason));
+ entry.pushKV("fee_reason", StringForFeeReason(res.GetObj().fee_calc.reason));
return entry;
}
return tx->GetHash().GetHex();
@@ -1635,7 +1633,7 @@ RPCHelpMan walletcreatefundedpsbt()
}, true
);
- UniValue options = request.params[3];
+ UniValue options{request.params[3].isNull() ? UniValue::VOBJ : request.params[3]};
CAmount fee;
int change_position;
diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
index fae9bf3ea5..0b52dcc001 100644
--- a/src/wallet/rpc/transactions.cpp
+++ b/src/wallet/rpc/transactions.cpp
@@ -85,14 +85,12 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
filter |= ISMINE_WATCH_ONLY;
}
- bool has_filtered_address = false;
- CTxDestination filtered_address = CNoDestination();
+ std::optional<CTxDestination> filtered_address{std::nullopt};
if (!by_label && !params[3].isNull() && !params[3].get_str().empty()) {
if (!IsValidDestinationString(params[3].get_str())) {
throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid");
}
filtered_address = DecodeDestination(params[3].get_str());
- has_filtered_address = true;
}
// Tally
@@ -106,23 +104,21 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
// Coinbase with less than 1 confirmation is no longer in the main chain
if ((wtx.IsCoinBase() && (nDepth < 1))
- || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase))
- {
+ || (wallet.IsTxImmatureCoinBase(wtx) && !include_immature_coinbase)) {
continue;
}
- for (const CTxOut& txout : wtx.tx->vout)
- {
+ for (const CTxOut& txout : wtx.tx->vout) {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address))
continue;
- if (has_filtered_address && !(filtered_address == address)) {
+ if (filtered_address && !(filtered_address == address)) {
continue;
}
isminefilter mine = wallet.IsMine(address);
- if(!(mine & filter))
+ if (!(mine & filter))
continue;
tallyitem& item = mapTally[address];
@@ -138,70 +134,55 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, cons
UniValue ret(UniValue::VARR);
std::map<std::string, tallyitem> label_tally;
- // Create m_address_book iterator
- // If we aren't filtering, go from begin() to end()
- auto start = wallet.m_address_book.begin();
- auto end = wallet.m_address_book.end();
- // If we are filtering, find() the applicable entry
- if (has_filtered_address) {
- start = wallet.m_address_book.find(filtered_address);
- if (start != end) {
- end = std::next(start);
- }
- }
+ const auto& func = [&](const CTxDestination& address, const std::string& label, const std::string& purpose, bool is_change) {
+ if (is_change) return; // no change addresses
- for (auto item_it = start; item_it != end; ++item_it)
- {
- if (item_it->second.IsChange()) continue;
- const CTxDestination& address = item_it->first;
- const std::string& label = item_it->second.GetLabel();
auto it = mapTally.find(address);
if (it == mapTally.end() && !fIncludeEmpty)
- continue;
+ return;
CAmount nAmount = 0;
int nConf = std::numeric_limits<int>::max();
bool fIsWatchonly = false;
- if (it != mapTally.end())
- {
+ if (it != mapTally.end()) {
nAmount = (*it).second.nAmount;
nConf = (*it).second.nConf;
fIsWatchonly = (*it).second.fIsWatchonly;
}
- if (by_label)
- {
+ if (by_label) {
tallyitem& _item = label_tally[label];
_item.nAmount += nAmount;
_item.nConf = std::min(_item.nConf, nConf);
_item.fIsWatchonly = fIsWatchonly;
- }
- else
- {
+ } else {
UniValue obj(UniValue::VOBJ);
- if(fIsWatchonly)
- obj.pushKV("involvesWatchonly", true);
+ if (fIsWatchonly) obj.pushKV("involvesWatchonly", true);
obj.pushKV("address", EncodeDestination(address));
obj.pushKV("amount", ValueFromAmount(nAmount));
obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf));
obj.pushKV("label", label);
UniValue transactions(UniValue::VARR);
- if (it != mapTally.end())
- {
- for (const uint256& _item : (*it).second.txids)
- {
+ if (it != mapTally.end()) {
+ for (const uint256& _item : (*it).second.txids) {
transactions.push_back(_item.GetHex());
}
}
obj.pushKV("txids", transactions);
ret.push_back(obj);
}
+ };
+
+ if (filtered_address) {
+ const auto& entry = wallet.FindAddressBookEntry(*filtered_address, /*allow_change=*/false);
+ if (entry) func(*filtered_address, entry->GetLabel(), entry->purpose, /*is_change=*/false);
+ } else {
+ // No filtered addr, walk-through the addressbook entry
+ wallet.ForEachAddrBookEntry(func);
}
- if (by_label)
- {
- for (const auto& entry : label_tally)
- {
+ if (by_label) {
+ for (const auto& entry : label_tally) {
CAmount nAmount = entry.second.nAmount;
int nConf = entry.second.nConf;
UniValue obj(UniValue::VOBJ);
@@ -329,11 +310,12 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest)
* @param wtx The wallet transaction.
* @param nMinDepth The minimum confirmation depth.
* @param fLong Whether to include the JSON version of the transaction.
- * @param ret The UniValue into which the result is stored.
+ * @param ret The vector into which the result is stored.
* @param filter_ismine The "is mine" filter flags.
* @param filter_label Optional label string to filter incoming transactions.
*/
-static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+template <class Vec>
+static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nMinDepth, bool fLong, Vec& ret, const isminefilter& filter_ismine, const std::string* filter_label) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
CAmount nFee;
std::list<COutputEntry> listReceived;
@@ -519,8 +501,7 @@ RPCHelpMan listtransactions()
if (nFrom < 0)
throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from");
- UniValue ret(UniValue::VARR);
-
+ std::vector<UniValue> ret;
{
LOCK(pwallet->cs_wallet);
@@ -542,9 +523,9 @@ RPCHelpMan listtransactions()
if ((nFrom + nCount) > (int)ret.size())
nCount = ret.size() - nFrom;
- const std::vector<UniValue>& txs = ret.getValues();
+ auto txs_rev_it{std::make_move_iterator(ret.rend())};
UniValue result{UniValue::VARR};
- result.push_backV({ txs.rend() - nFrom - nCount, txs.rend() - nFrom }); // Return oldest to newest
+ result.push_backV(txs_rev_it - nFrom - nCount, txs_rev_it - nFrom); // Return oldest to newest
return result;
},
};
@@ -908,7 +889,7 @@ RPCHelpMan rescanblockchain()
}
CWallet::ScanResult result =
- pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, true /* fUpdate */);
+ pwallet->ScanForWalletTransactions(start_block, start_height, stop_height, reserver, /*fUpdate=*/true, /*save_progress=*/false);
switch (result.status) {
case CWallet::ScanResult::SUCCESS:
break;
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 1fec82a485..bba31dfe0b 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -21,26 +21,22 @@ namespace wallet {
//! Value for the first BIP 32 hardened derivation. Can be used as a bit mask and as a value. See BIP 32 for more details.
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
-bool LegacyScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
+BResult<CTxDestination> LegacyScriptPubKeyMan::GetNewDestination(const OutputType type)
{
if (LEGACY_OUTPUT_TYPES.count(type) == 0) {
- error = _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");
- return false;
+ return _("Error: Legacy wallets only support the \"legacy\", \"p2sh-segwit\", and \"bech32\" address types");;
}
assert(type != OutputType::BECH32M);
LOCK(cs_KeyStore);
- error.clear();
// Generate a new key that is added to wallet
CPubKey new_key;
if (!GetKeyFromPool(new_key, type)) {
- error = _("Error: Keypool ran out, please call keypoolrefill first");
- return false;
+ return _("Error: Keypool ran out, please call keypoolrefill first");
}
LearnRelatedScripts(new_key, type);
- dest = GetDestinationForKey(new_key, type);
- return true;
+ return GetDestinationForKey(new_key, type);
}
typedef std::vector<unsigned char> valtype;
@@ -1658,12 +1654,11 @@ std::set<CKeyID> LegacyScriptPubKeyMan::GetKeys() const
return set_address;
}
-bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
+BResult<CTxDestination> DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type)
{
// Returns true if this descriptor supports getting new addresses. Conditions where we may be unable to fetch them (e.g. locked) are caught later
if (!CanGetAddresses()) {
- error = _("No addresses available");
- return false;
+ return _("No addresses available");
}
{
LOCK(cs_desc_man);
@@ -1681,15 +1676,14 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
std::vector<CScript> scripts_temp;
if (m_wallet_descriptor.range_end <= m_max_cached_index && !TopUp(1)) {
// We can't generate anymore keys
- error = _("Error: Keypool ran out, please call keypoolrefill first");
- return false;
+ return _("Error: Keypool ran out, please call keypoolrefill first");
}
if (!m_wallet_descriptor.descriptor->ExpandFromCache(m_wallet_descriptor.next_index, m_wallet_descriptor.cache, scripts_temp, out_keys)) {
// We can't generate anymore keys
- error = _("Error: Keypool ran out, please call keypoolrefill first");
- return false;
+ return _("Error: Keypool ran out, please call keypoolrefill first");
}
+ CTxDestination dest;
std::optional<OutputType> out_script_type = m_wallet_descriptor.descriptor->GetOutputType();
if (out_script_type && out_script_type == type) {
ExtractDestination(scripts_temp[0], dest);
@@ -1698,7 +1692,7 @@ bool DescriptorScriptPubKeyMan::GetNewDestination(const OutputType type, CTxDest
}
m_wallet_descriptor.next_index++;
WalletBatch(m_storage.GetDatabase()).WriteDescriptor(GetID(), m_wallet_descriptor);
- return true;
+ return dest;
}
}
@@ -1769,9 +1763,14 @@ bool DescriptorScriptPubKeyMan::Encrypt(const CKeyingMaterial& master_key, Walle
bool DescriptorScriptPubKeyMan::GetReservedDestination(const OutputType type, bool internal, CTxDestination& address, int64_t& index, CKeyPool& keypool, bilingual_str& error)
{
LOCK(cs_desc_man);
- bool result = GetNewDestination(type, address, error);
+ auto op_dest = GetNewDestination(type);
index = m_wallet_descriptor.next_index - 1;
- return result;
+ if (op_dest) {
+ address = op_dest.GetObj();
+ } else {
+ error = op_dest.GetError();
+ }
+ return op_dest.HasRes();
}
void DescriptorScriptPubKeyMan::ReturnDestination(int64_t index, bool internal, const CTxDestination& addr)
diff --git a/src/wallet/scriptpubkeyman.h b/src/wallet/scriptpubkeyman.h
index ad924791af..eebc05330f 100644
--- a/src/wallet/scriptpubkeyman.h
+++ b/src/wallet/scriptpubkeyman.h
@@ -11,6 +11,7 @@
#include <script/standard.h>
#include <util/error.h>
#include <util/message.h>
+#include <util/result.h>
#include <util/time.h>
#include <wallet/crypter.h>
#include <wallet/ismine.h>
@@ -171,7 +172,7 @@ protected:
public:
explicit ScriptPubKeyMan(WalletStorage& storage) : m_storage(storage) {}
virtual ~ScriptPubKeyMan() {};
- virtual bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) { return false; }
+ virtual BResult<CTxDestination> GetNewDestination(const OutputType type) { return Untranslated("Not supported"); }
virtual isminetype IsMine(const CScript& script) const { return ISMINE_NO; }
//! Check that the given decryption key is valid for this ScriptPubKeyMan, i.e. it decrypts all of the keys handled by it.
@@ -359,7 +360,7 @@ private:
public:
using ScriptPubKeyMan::ScriptPubKeyMan;
- bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
+ BResult<CTxDestination> GetNewDestination(const OutputType type) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
@@ -567,7 +568,7 @@ public:
mutable RecursiveMutex cs_desc_man;
- bool GetNewDestination(const OutputType type, CTxDestination& dest, bilingual_str& error) override;
+ BResult<CTxDestination> GetNewDestination(const OutputType type) override;
isminetype IsMine(const CScript& script) const override;
bool CheckDecryptionKey(const CKeyingMaterial& master_key, bool accept_no_keys = false) override;
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 81055a5a1a..9be29c4709 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -27,25 +27,20 @@ using interfaces::FoundBlock;
namespace wallet {
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
-int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig)
-{
- return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
-}
-
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* provider, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* provider, const CCoinControl* coin_control)
{
CMutableTransaction txn;
- txn.vin.push_back(CTxIn(COutPoint()));
- if (!provider || !DummySignInput(*provider, txn.vin[0], txout, use_max_sig)) {
+ txn.vin.push_back(CTxIn(outpoint));
+ if (!provider || !DummySignInput(*provider, txn.vin[0], txout, coin_control)) {
return -1;
}
return GetVirtualTransactionInputSize(txn.vin[0]);
}
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig)
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, const CCoinControl* coin_control)
{
const std::unique_ptr<SigningProvider> provider = wallet->GetSolvingProvider(txout.scriptPubKey);
- return CalculateMaximumSignedInputSize(txout, provider.get(), use_max_sig);
+ return CalculateMaximumSignedInputSize(txout, COutPoint(), provider.get(), coin_control);
}
// txouts needs to be in the order of tx.vin
@@ -198,7 +193,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
// Filter by spendable outputs only
if (!spendable && only_spendable) continue;
- int input_bytes = GetTxSpendSize(wallet, wtx, i, (coinControl && coinControl->fAllowWatchOnly));
+ int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
result.total_amount += output.nValue;
@@ -289,8 +284,9 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
) {
CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(wallet, *wtx.tx, output.n).scriptPubKey, address)) {
+ const auto out = wtx.tx->vout.at(output.n);
result[address].emplace_back(
- COutPoint(wtx.GetHash(), output.n), wtx.tx->vout.at(output.n), depth, GetTxSpendSize(wallet, wtx, output.n), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
+ COutPoint(wtx.GetHash(), output.n), out, depth, CalculateMaximumSignedInputSize(out, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ false, wtx.GetTxTime(), CachedTxIsFromMe(wallet, wtx, ISMINE_ALL));
}
}
}
@@ -397,10 +393,13 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here.
std::vector<OutputGroup> all_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, false /* positive_only */);
+ CAmount target_with_change = nTargetValue;
// While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output.
- // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
- if (auto knapsack_result{KnapsackSolver(all_groups, nTargetValue + coin_selection_params.m_change_fee,
- coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
+ // So we need to include that for KnapsackSolver and SRD as well, as we are expecting to create a change output.
+ if (!coin_selection_params.m_subtract_fee_outputs) {
+ target_with_change += coin_selection_params.m_change_fee;
+ }
+ if (auto knapsack_result{KnapsackSolver(all_groups, target_with_change, coin_selection_params.m_min_change_target, coin_selection_params.rng_fast)}) {
knapsack_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*knapsack_result);
}
@@ -409,7 +408,7 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
// barely meets the target. Just use the lower bound change target instead of the randomly
// generated one, since SRD will result in a random change amount anyway; avoid making the
// target needlessly large.
- const CAmount srd_target = nTargetValue + coin_selection_params.m_change_fee + CHANGE_LOWER;
+ const CAmount srd_target = target_with_change + CHANGE_LOWER;
if (auto srd_result{SelectCoinsSRD(positive_groups, srd_target, coin_selection_params.rng_fast)}) {
srd_result->ComputeAndSetWaste(coin_selection_params.m_cost_of_change);
results.push_back(*srd_result);
@@ -447,14 +446,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, const std::vec
if (ptr_wtx->tx->vout.size() <= outpoint.n) {
return std::nullopt;
}
- input_bytes = GetTxSpendSize(wallet, *ptr_wtx, outpoint.n, false);
txout = ptr_wtx->tx->vout.at(outpoint.n);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
} else {
// The input is external. We did not find the tx in mapWallet.
if (!coin_control.GetExternalOutput(outpoint, txout)) {
return std::nullopt;
}
- input_bytes = CalculateMaximumSignedInputSize(txout, &coin_control.m_external_provider, /*use_max_sig=*/true);
+ input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
}
// If available, override calculated size with coin control specified size
if (coin_control.HasInputWeight(outpoint)) {
@@ -653,19 +652,16 @@ static void DiscourageFeeSniping(CMutableTransaction& tx, FastRandomContext& rng
}
}
-static std::optional<CreatedTransactionResult> CreateTransactionInternal(
+static BResult<CreatedTransactionResult> CreateTransactionInternal(
CWallet& wallet,
const std::vector<CRecipient>& vecSend,
int change_pos,
- bilingual_str& error,
const CCoinControl& coin_control,
- FeeCalculation& fee_calc_out,
bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{
AssertLockHeld(wallet.cs_wallet);
// out variables, to be packed into returned result structure
- CTransactionRef tx;
CAmount nFeeRet;
int nChangePosInOut = change_pos;
@@ -694,6 +690,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Create change script that will be used if we need change
CScript scriptChange;
+ bilingual_str error; // possible error str
// coin control: send change to custom address
if (!std::get_if<CNoDestination>(&coin_control.destChange)) {
@@ -741,13 +738,11 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
// provided one
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
- error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
- return std::nullopt;
+ return strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
}
if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
// eventually allow a fallback fee
- error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
- return std::nullopt;
+ return _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
}
// Calculate the cost of change
@@ -772,10 +767,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
}
- if (IsDust(txout, wallet.chain().relayDustFee()))
- {
- error = _("Transaction amount too small");
- return std::nullopt;
+ if (IsDust(txout, wallet.chain().relayDustFee())) {
+ return _("Transaction amount too small");
}
txNew.vout.push_back(txout);
}
@@ -796,8 +789,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Choose coins to use
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
if (!result) {
- error = _("Insufficient funds");
- return std::nullopt;
+ return _("Insufficient funds");
}
TRACE5(coin_selection, selected_coins, wallet.GetName().c_str(), GetAlgorithmName(result->m_algo).c_str(), result->m_target, result->GetWaste(), result->GetSelectedValue());
@@ -811,10 +803,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Insert change txn at random position:
nChangePosInOut = rng_fast.randrange(txNew.vout.size() + 1);
}
- else if ((unsigned int)nChangePosInOut > txNew.vout.size())
- {
- error = _("Transaction change output index out of range");
- return std::nullopt;
+ else if ((unsigned int)nChangePosInOut > txNew.vout.size()) {
+ return _("Transaction change output index out of range");
}
assert(nChangePosInOut != -1);
@@ -841,8 +831,7 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, &coin_control);
int nBytes = tx_sizes.vsize;
if (nBytes == -1) {
- error = _("Missing solving data for estimating transaction size");
- return std::nullopt;
+ return _("Missing solving data for estimating transaction size");
}
nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes);
@@ -902,11 +891,10 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Error if this output is reduced to be below dust
if (IsDust(txout, wallet.chain().relayDustFee())) {
if (txout.nValue < 0) {
- error = _("The transaction amount is too small to pay the fee");
+ return _("The transaction amount is too small to pay the fee");
} else {
- error = _("The transaction amount is too small to send after the fee has been deducted");
+ return _("The transaction amount is too small to send after the fee has been deducted");
}
- return std::nullopt;
}
}
++i;
@@ -916,42 +904,37 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// Give up if change keypool ran out and change is required
if (scriptChange.empty() && nChangePosInOut != -1) {
- return std::nullopt;
+ return error;
}
if (sign && !wallet.SignTransaction(txNew)) {
- error = _("Signing transaction failed");
- return std::nullopt;
+ return _("Signing transaction failed");
}
// Return the constructed transaction data.
- tx = MakeTransactionRef(std::move(txNew));
+ CTransactionRef tx = MakeTransactionRef(std::move(txNew));
// Limit size
if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) ||
(!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT))
{
- error = _("Transaction too large");
- return std::nullopt;
+ return _("Transaction too large");
}
if (nFeeRet > wallet.m_default_max_tx_fee) {
- error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
- return std::nullopt;
+ return TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
}
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
if (!wallet.chain().checkChainLimits(tx)) {
- error = _("Transaction has too long of a mempool chain");
- return std::nullopt;
+ return _("Transaction has too long of a mempool chain");
}
}
// Before we return success, we assume any change key will be used to prevent
// accidental re-use.
reservedest.KeepDestination();
- fee_calc_out = feeCalc;
wallet.WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
@@ -961,52 +944,50 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
feeCalc.est.fail.start, feeCalc.est.fail.end,
(feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0,
feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool);
- return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut);
+ return CreatedTransactionResult(tx, nFeeRet, nChangePosInOut, feeCalc);
}
-std::optional<CreatedTransactionResult> CreateTransaction(
+BResult<CreatedTransactionResult> CreateTransaction(
CWallet& wallet,
const std::vector<CRecipient>& vecSend,
int change_pos,
- bilingual_str& error,
const CCoinControl& coin_control,
- FeeCalculation& fee_calc_out,
bool sign)
{
if (vecSend.empty()) {
- error = _("Transaction must have at least one recipient");
- return std::nullopt;
+ return _("Transaction must have at least one recipient");
}
if (std::any_of(vecSend.cbegin(), vecSend.cend(), [](const auto& recipient){ return recipient.nAmount < 0; })) {
- error = _("Transaction amounts must not be negative");
- return std::nullopt;
+ return _("Transaction amounts must not be negative");
}
LOCK(wallet.cs_wallet);
- std::optional<CreatedTransactionResult> txr_ungrouped = CreateTransactionInternal(wallet, vecSend, change_pos, error, coin_control, fee_calc_out, sign);
- TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), txr_ungrouped.has_value(),
- txr_ungrouped.has_value() ? txr_ungrouped->fee : 0, txr_ungrouped.has_value() ? txr_ungrouped->change_pos : 0);
- if (!txr_ungrouped) return std::nullopt;
+ auto res = CreateTransactionInternal(wallet, vecSend, change_pos, coin_control, sign);
+ TRACE4(coin_selection, normal_create_tx_internal, wallet.GetName().c_str(), res.HasRes(),
+ res ? res.GetObj().fee : 0, res ? res.GetObj().change_pos : 0);
+ if (!res) return res;
+ const auto& txr_ungrouped = res.GetObj();
// try with avoidpartialspends unless it's enabled already
- if (txr_ungrouped->fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
+ if (txr_ungrouped.fee > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
TRACE1(coin_selection, attempting_aps_create_tx, wallet.GetName().c_str());
CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true;
- bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
- std::optional<CreatedTransactionResult> txr_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, error2, tmp_cc, fee_calc_out, sign);
+ auto res_tx_grouped = CreateTransactionInternal(wallet, vecSend, change_pos, tmp_cc, sign);
+ // Helper optional class for now
+ std::optional<CreatedTransactionResult> txr_grouped{res_tx_grouped.HasRes() ? std::make_optional(res_tx_grouped.GetObj()) : std::nullopt};
// if fee of this alternative one is within the range of the max fee, we use this one
- const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped->fee + wallet.m_max_aps_fee) : false};
+ const bool use_aps{txr_grouped.has_value() ? (txr_grouped->fee <= txr_ungrouped.fee + wallet.m_max_aps_fee) : false};
TRACE5(coin_selection, aps_create_tx_internal, wallet.GetName().c_str(), use_aps, txr_grouped.has_value(),
txr_grouped.has_value() ? txr_grouped->fee : 0, txr_grouped.has_value() ? txr_grouped->change_pos : 0);
if (txr_grouped) {
wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n",
- txr_ungrouped->fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
- if (use_aps) return txr_grouped;
+ txr_ungrouped.fee, txr_grouped->fee, use_aps ? "grouped" : "non-grouped");
+ if (use_aps) return res_tx_grouped;
}
}
- return txr_ungrouped;
+ return res;
}
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
@@ -1042,12 +1023,15 @@ bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet,
}
}
- FeeCalculation fee_calc_out;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(wallet, vecSend, nChangePosInOut, error, coinControl, fee_calc_out, false);
- if (!txr) return false;
- CTransactionRef tx_new = txr->tx;
- nFeeRet = txr->fee;
- nChangePosInOut = txr->change_pos;
+ auto res = CreateTransaction(wallet, vecSend, nChangePosInOut, coinControl, false);
+ if (!res) {
+ error = res.GetError();
+ return false;
+ }
+ const auto& txr = res.GetObj();
+ CTransactionRef tx_new = txr.tx;
+ nFeeRet = txr.fee;
+ nChangePosInOut = txr.change_pos;
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]);
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index cba42d6fae..ab0ff1ee58 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -6,6 +6,8 @@
#define BITCOIN_WALLET_SPEND_H
#include <consensus/amount.h>
+#include <policy/fees.h> // for FeeCalculation
+#include <util/result.h>
#include <wallet/coinselection.h>
#include <wallet/transaction.h>
#include <wallet/wallet.h>
@@ -14,23 +16,16 @@
namespace wallet {
/** Get the marginal bytes if spending the specified output from this transaction.
- * use_max_sig indicates whether to use the maximum sized, 72 byte signature when calculating the
- * size of the input spend. This should only be set when watch-only outputs are allowed */
-int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
-
-//Get the marginal bytes of spending the specified output
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const SigningProvider* pwallet, bool use_max_sig = false);
-
+ * Use CoinControl to determine whether to expect signature grinding when calculating the size of the input spend. */
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, const CCoinControl* coin_control = nullptr);
+int CalculateMaximumSignedInputSize(const CTxOut& txout, const COutPoint outpoint, const SigningProvider* pwallet, const CCoinControl* coin_control = nullptr);
struct TxSize {
int64_t vsize{-1};
int64_t weight{-1};
};
-/** Calculate the size of the transaction assuming all signatures are max size
-* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
-* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
-* be AllInputsMine). */
+/** Calculate the size of the transaction using CoinControl to determine
+ * whether to expect signature grinding when calculating the size of the input spend. */
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
@@ -107,10 +102,11 @@ struct CreatedTransactionResult
{
CTransactionRef tx;
CAmount fee;
+ FeeCalculation fee_calc;
int change_pos;
- CreatedTransactionResult(CTransactionRef tx, CAmount fee, int change_pos)
- : tx(tx), fee(fee), change_pos(change_pos) {}
+ CreatedTransactionResult(CTransactionRef _tx, CAmount _fee, int _change_pos, const FeeCalculation& _fee_calc)
+ : tx(_tx), fee(_fee), fee_calc(_fee_calc), change_pos(_change_pos) {}
};
/**
@@ -118,7 +114,7 @@ struct CreatedTransactionResult
* selected by SelectCoins(); Also create the change output, when needed
* @note passing change_pos as -1 will result in setting a random position
*/
-std::optional<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
+BResult<CreatedTransactionResult> CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, int change_pos, const CCoinControl& coin_control, bool sign = true);
/**
* Insert additional inputs into the transaction by
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 27202cd7f3..a418105ee1 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -74,11 +74,9 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue;
if (spendable) {
- CTxDestination dest;
- bilingual_str error;
- const bool destination_ok = wallet.GetNewDestination(OutputType::BECH32, "", dest, error);
- assert(destination_ok);
- tx.vout[nInput].scriptPubKey = GetScriptForDestination(dest);
+ auto op_dest = wallet.GetNewDestination(OutputType::BECH32, "");
+ assert(op_dest.HasRes());
+ tx.vout[nInput].scriptPubKey = GetScriptForDestination(op_dest.GetObj());
}
uint256 txid = tx.GetHash();
@@ -86,7 +84,8 @@ static void add_coin(std::vector<COutput>& coins, CWallet& wallet, const CAmount
auto ret = wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(txid), std::forward_as_tuple(MakeTransactionRef(std::move(tx)), TxStateInactive{}));
assert(ret.second);
CWalletTx& wtx = (*ret.first).second;
- coins.emplace_back(COutPoint(wtx.GetHash(), nInput), wtx.tx->vout.at(nInput), nAge, GetTxSpendSize(wallet, wtx, nInput), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
+ const auto& txout = wtx.tx->vout.at(nInput);
+ coins.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
@@ -866,7 +865,23 @@ BOOST_AUTO_TEST_CASE(waste_test)
const CAmount new_target{in_amt - fee * 2 - fee_diff * 2};
add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
- BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /* change cost */ 0, new_target));
+ BOOST_CHECK_EQUAL(0, GetSelectionWaste(selection, /*change_cost=*/ 0, new_target));
+ selection.clear();
+
+ // Negative waste when the long term fee is greater than the current fee and the selected value == target
+ const CAmount exact_target1{3 * COIN - 2 * fee};
+ const CAmount target_waste1{-2 * fee_diff}; // = (2 * fee) - (2 * (fee + fee_diff))
+ add_coin(1 * COIN, 1, selection, fee, fee + fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + fee_diff);
+ BOOST_CHECK_EQUAL(target_waste1, GetSelectionWaste(selection, /*change_cost=*/ 0, exact_target1));
+ selection.clear();
+
+ // Negative waste when the long term fee is greater than the current fee and change_cost < - (inputs * (fee - long_term_fee))
+ const CAmount large_fee_diff{90};
+ const CAmount target_waste2{-2 * large_fee_diff + change_cost}; // = (2 * fee) - (2 * (fee + large_fee_diff)) + change_cost
+ add_coin(1 * COIN, 1, selection, fee, fee + large_fee_diff);
+ add_coin(2 * COIN, 2, selection, fee, fee + large_fee_diff);
+ BOOST_CHECK_EQUAL(target_waste2, GetSelectionWaste(selection, change_cost, target));
}
BOOST_AUTO_TEST_CASE(effective_value_test)
diff --git a/src/wallet/test/fuzz/notifications.cpp b/src/wallet/test/fuzz/notifications.cpp
index 9089c8ff46..5c173773e4 100644
--- a/src/wallet/test/fuzz/notifications.cpp
+++ b/src/wallet/test/fuzz/notifications.cpp
@@ -69,15 +69,14 @@ struct FuzzedWallet {
CScript GetScriptPubKey(FuzzedDataProvider& fuzzed_data_provider)
{
auto type{fuzzed_data_provider.PickValueInArray(OUTPUT_TYPES)};
- CTxDestination dest;
- bilingual_str error;
+ BResult<CTxDestination> op_dest;
if (fuzzed_data_provider.ConsumeBool()) {
- assert(wallet->GetNewDestination(type, "", dest, error));
+ op_dest = wallet->GetNewDestination(type, "");
} else {
- assert(wallet->GetNewChangeDestination(type, dest, error));
+ op_dest = wallet->GetNewChangeDestination(type);
}
- assert(error.empty());
- return GetScriptForDestination(dest);
+ assert(op_dest.HasRes());
+ return GetScriptForDestination(op_dest.GetObj());
}
};
@@ -138,8 +137,13 @@ FUZZ_TARGET_INIT(wallet_notifications, initialize_setup)
block.vtx.emplace_back(MakeTransactionRef(tx));
}
// Mine block
- a.wallet->blockConnected(block, chain.size());
- b.wallet->blockConnected(block, chain.size());
+ const uint256& hash = block.GetHash();
+ interfaces::BlockInfo info{hash};
+ info.prev_hash = &block.hashPrevBlock;
+ info.height = chain.size();
+ info.data = &block;
+ a.wallet->blockConnected(info);
+ b.wallet->blockConnected(info);
// Store the coins for the next block
Coins coins_new;
for (const auto& tx : block.vtx) {
@@ -155,8 +159,13 @@ FUZZ_TARGET_INIT(wallet_notifications, initialize_setup)
auto& [coins, block]{chain.back()};
if (block.vtx.empty()) return; // Can only disconnect if the block was submitted first
// Disconnect block
- a.wallet->blockDisconnected(block, chain.size() - 1);
- b.wallet->blockDisconnected(block, chain.size() - 1);
+ const uint256& hash = block.GetHash();
+ interfaces::BlockInfo info{hash};
+ info.prev_hash = &block.hashPrevBlock;
+ info.height = chain.size() - 1;
+ info.data = &block;
+ a.wallet->blockDisconnected(info);
+ b.wallet->blockDisconnected(info);
chain.pop_back();
});
auto& [coins, first_block]{chain.front()};
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
index bdc148afb4..53c3b5d2ae 100644
--- a/src/wallet/test/spend_tests.cpp
+++ b/src/wallet/test/spend_tests.cpp
@@ -28,19 +28,18 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
auto check_tx = [&wallet](CAmount leftover_input_amount) {
CRecipient recipient{GetScriptForRawPubKey({}), 50 * COIN - leftover_input_amount, true /* subtract fee */};
constexpr int RANDOM_CHANGE_POSITION = -1;
- bilingual_str error;
CCoinControl coin_control;
coin_control.m_feerate.emplace(10000);
coin_control.fOverrideFeeRate = true;
// We need to use a change type with high cost of change so that the leftover amount will be dropped to fee instead of added as a change output
coin_control.m_change_type = OutputType::LEGACY;
- FeeCalculation fee_calc;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, coin_control, fee_calc);
- BOOST_CHECK(txr.has_value());
- BOOST_CHECK_EQUAL(txr->tx->vout.size(), 1);
- BOOST_CHECK_EQUAL(txr->tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr->fee);
- BOOST_CHECK_GT(txr->fee, 0);
- return txr->fee;
+ auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, coin_control);
+ BOOST_CHECK(res);
+ const auto& txr = res.GetObj();
+ BOOST_CHECK_EQUAL(txr.tx->vout.size(), 1);
+ BOOST_CHECK_EQUAL(txr.tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - txr.fee);
+ BOOST_CHECK_GT(txr.fee, 0);
+ return txr.fee;
};
// Send full input amount to recipient, check that only nonzero fee is
diff --git a/src/wallet/test/util.cpp b/src/wallet/test/util.cpp
index aa3121511d..ab72721f9d 100644
--- a/src/wallet/test/util.cpp
+++ b/src/wallet/test/util.cpp
@@ -38,7 +38,7 @@ std::unique_ptr<CWallet> CreateSyncedWallet(interfaces::Chain& chain, CChain& cc
}
WalletRescanReserver reserver(*wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet->ScanForWalletTransactions(cchain.Genesis()->GetBlockHash(), /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK_EQUAL(result.last_scanned_block, cchain.Tip()->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, cchain.Height());
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index 27aa3f02e9..aa5df695de 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -4,7 +4,6 @@
#include <wallet/wallet.h>
-#include <any>
#include <future>
#include <memory>
#include <stdint.h>
@@ -13,7 +12,6 @@
#include <interfaces/chain.h>
#include <key_io.h>
#include <node/blockstorage.h>
-#include <node/context.h>
#include <policy/policy.h>
#include <rpc/server.h>
#include <test/util/logging.h>
@@ -55,9 +53,6 @@ static const std::shared_ptr<CWallet> TestLoadWallet(WalletContext& context)
auto database = MakeWalletDatabase("", options, status, error);
auto wallet = CWallet::Create(context, "", std::move(database), options.create_flags, error, warnings);
NotifyWalletLoaded(context, wallet);
- if (context.chain) {
- wallet->postInitProcess();
- }
return wallet;
}
@@ -112,7 +107,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions({} /* start_block */, 0 /* start_height */, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/{}, /*start_height=*/0, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull());
@@ -123,7 +118,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
// Verify ScanForWalletTransactions picks up transactions in both the old
// and new block files.
{
- CWallet wallet(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
+ CWallet wallet(m_node.chain.get(), "", m_args, CreateMockWalletDatabase());
{
LOCK(wallet.cs_wallet);
wallet.SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
@@ -131,13 +126,28 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
}
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
+ std::chrono::steady_clock::time_point fake_time;
+ reserver.setNow([&] { fake_time += 60s; return fake_time; });
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+
+ {
+ CBlockLocator locator;
+ BOOST_CHECK(!WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
+ BOOST_CHECK(locator.IsNull());
+ }
+
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/true);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::SUCCESS);
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);
+
+ {
+ CBlockLocator locator;
+ BOOST_CHECK(WalletBatch{wallet.GetDatabase()}.ReadBestBlock(locator));
+ BOOST_CHECK(!locator.IsNull());
+ }
}
// Prune the older block file.
@@ -161,7 +171,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
@@ -188,7 +198,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
AddKey(wallet, coinbaseKey);
WalletRescanReserver reserver(wallet);
reserver.reserve();
- CWallet::ScanResult result = wallet.ScanForWalletTransactions(oldTip->GetBlockHash(), oldTip->nHeight, {} /* max_height */, reserver, false /* update */);
+ CWallet::ScanResult result = wallet.ScanForWalletTransactions(/*start_block=*/oldTip->GetBlockHash(), /*start_height=*/oldTip->nHeight, /*max_height=*/{}, reserver, /*fUpdate=*/false, /*save_progress=*/false);
BOOST_CHECK_EQUAL(result.status, CWallet::ScanResult::FAILURE);
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull());
@@ -521,14 +531,12 @@ public:
CWalletTx& AddTx(CRecipient recipient)
{
CTransactionRef tx;
- bilingual_str error;
CCoinControl dummy;
- FeeCalculation fee_calc_out;
{
constexpr int RANDOM_CHANGE_POSITION = -1;
- std::optional<CreatedTransactionResult> txr = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, error, dummy, fee_calc_out);
- BOOST_CHECK(txr.has_value());
- tx = txr->tx;
+ auto res = CreateTransaction(*wallet, {recipient}, RANDOM_CHANGE_POSITION, dummy);
+ BOOST_CHECK(res);
+ tx = res.GetObj().tx;
}
wallet->CommitTransaction(tx, {}, {});
CMutableTransaction blocktx;
@@ -614,9 +622,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
BOOST_CHECK(!wallet->TopUpKeyPool(1000));
- CTxDestination dest;
- bilingual_str error;
- BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
+ BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
}
{
const std::shared_ptr<CWallet> wallet = std::make_shared<CWallet>(m_node.chain.get(), "", m_args, CreateDummyWalletDatabase());
@@ -624,9 +630,7 @@ BOOST_FIXTURE_TEST_CASE(wallet_disableprivkeys, TestChain100Setup)
wallet->SetWalletFlag(WALLET_FLAG_DESCRIPTORS);
wallet->SetMinVersion(FEATURE_LATEST);
wallet->SetWalletFlag(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
- CTxDestination dest;
- bilingual_str error;
- BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, "", dest, error));
+ BOOST_CHECK(!wallet->GetNewDestination(OutputType::BECH32, ""));
}
}
@@ -761,6 +765,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// being blocked
wallet = TestLoadWallet(context);
BOOST_CHECK(rescan_completed);
+ // AddToWallet events for block_tx and mempool_tx
BOOST_CHECK_EQUAL(addtx_count, 2);
{
LOCK(wallet->cs_wallet);
@@ -773,6 +778,8 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
// transactionAddedToMempool events are processed
promise.set_value();
SyncWithValidationInterfaceQueue();
+ // AddToWallet events for block_tx and mempool_tx events are counted a
+ // second time as the notificaiton queue is processed
BOOST_CHECK_EQUAL(addtx_count, 4);
@@ -796,7 +803,7 @@ BOOST_FIXTURE_TEST_CASE(CreateWallet, TestChain100Setup)
SyncWithValidationInterfaceQueue();
});
wallet = TestLoadWallet(context);
- BOOST_CHECK_EQUAL(addtx_count, 4);
+ BOOST_CHECK_EQUAL(addtx_count, 2);
{
LOCK(wallet->cs_wallet);
BOOST_CHECK_EQUAL(wallet->mapWallet.count(block_tx.GetHash()), 1U);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 041481559b..54a3221e2d 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1314,30 +1314,31 @@ void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRe
}
}
-void CWallet::blockConnected(const CBlock& block, int height)
+void CWallet::blockConnected(const interfaces::BlockInfo& block)
{
- const uint256& block_hash = block.GetHash();
+ assert(block.data);
LOCK(cs_wallet);
- m_last_block_processed_height = height;
- m_last_block_processed = block_hash;
- for (size_t index = 0; index < block.vtx.size(); index++) {
- SyncTransaction(block.vtx[index], TxStateConfirmed{block_hash, height, static_cast<int>(index)});
- transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
+ m_last_block_processed_height = block.height;
+ m_last_block_processed = block.hash;
+ for (size_t index = 0; index < block.data->vtx.size(); index++) {
+ SyncTransaction(block.data->vtx[index], TxStateConfirmed{block.hash, block.height, static_cast<int>(index)});
+ transactionRemovedFromMempool(block.data->vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */);
}
}
-void CWallet::blockDisconnected(const CBlock& block, int height)
+void CWallet::blockDisconnected(const interfaces::BlockInfo& block)
{
+ assert(block.data);
LOCK(cs_wallet);
// At block disconnection, this will change an abandoned transaction to
// be unconfirmed, whether or not the transaction is added back to the mempool.
// User may have to call abandontransaction again. It may be addressed in the
// future with a stickier abandoned state or even removing abandontransaction call.
- m_last_block_processed_height = height - 1;
- m_last_block_processed = block.hashPrevBlock;
- for (const CTransactionRef& ptx : block.vtx) {
+ m_last_block_processed_height = block.height - 1;
+ m_last_block_processed = *Assert(block.prev_hash);
+ for (const CTransactionRef& ptx : Assert(block.data)->vtx) {
SyncTransaction(ptx, TxStateInactive{});
}
}
@@ -1504,13 +1505,16 @@ bool CWallet::AddWalletFlags(uint64_t flags)
}
// 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 DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig)
+// or a max-sized low-S signature (e.g. 72 bytes) depending on coin_control
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control)
{
// Fill in dummy signatures for fee calculation.
const CScript& scriptPubKey = txout.scriptPubKey;
SignatureData sigdata;
+ // Use max sig if watch only inputs were used or if this particular input is an external input
+ // to ensure a sufficient fee is attained for the requested feerate.
+ const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(tx_in.prevout));
if (!ProduceSignature(provider, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) {
return false;
}
@@ -1577,12 +1581,9 @@ bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut>
nIn++;
continue;
}
- // Use max sig if watch only inputs were used or if this particular input is an external input
- // to ensure a sufficient fee is attained for the requested feerate.
- const bool use_max_sig = coin_control && (coin_control->fAllowWatchOnly || coin_control->IsExternalSelected(txin.prevout));
const std::unique_ptr<SigningProvider> provider = GetSolvingProvider(txout.scriptPubKey);
- if (!provider || !DummySignInput(*provider, txin, txout, use_max_sig)) {
- if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, use_max_sig)) {
+ if (!provider || !DummySignInput(*provider, txin, txout, coin_control)) {
+ if (!coin_control || !DummySignInput(coin_control->m_external_provider, txin, txout, coin_control)) {
return false;
}
}
@@ -1665,7 +1666,7 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
if (start) {
// TODO: this should take into account failure by ScanResult::USER_ABORT
- ScanResult result = ScanForWalletTransactions(start_block, start_height, {} /* max_height */, reserver, update);
+ ScanResult result = ScanForWalletTransactions(start_block, start_height, /*max_height=*/{}, reserver, /*fUpdate=*/update, /*save_progress=*/false);
if (result.status == ScanResult::FAILURE) {
int64_t time_max;
CHECK_NONFATAL(chain().findBlock(result.last_failed_block, FoundBlock().maxTime(time_max)));
@@ -1678,7 +1679,8 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
/**
* 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.
+ * exist in the wallet will be updated. If max_height is not set, the
+ * mempool will be scanned as well.
*
* @param[in] start_block Scan starting block. If block is not on the active
* chain, the scan will return SUCCESS immediately.
@@ -1696,12 +1698,11 @@ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver& r
* the main chain after to the addition of any new keys you want to detect
* transactions for.
*/
-CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate)
+CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress)
{
- using Clock = std::chrono::steady_clock;
- constexpr auto LOG_INTERVAL{60s};
- auto current_time{Clock::now()};
- auto start_time{Clock::now()};
+ constexpr auto INTERVAL_TIME{60s};
+ auto current_time{reserver.now()};
+ auto start_time{reserver.now()};
assert(reserver.isReserved());
@@ -1728,8 +1729,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
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 (Clock::now() >= current_time + LOG_INTERVAL) {
- current_time = Clock::now();
+
+ bool next_interval = reserver.now() >= current_time + INTERVAL_TIME;
+ if (next_interval) {
+ current_time = reserver.now();
WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", block_height, progress_current);
}
@@ -1759,6 +1762,16 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
// scan succeeded, record block as most recent successfully scanned
result.last_scanned_block = block_hash;
result.last_scanned_height = block_height;
+
+ if (save_progress && next_interval) {
+ CBlockLocator loc = m_chain->getActiveChainLocator(block_hash);
+
+ if (!loc.IsNull()) {
+ WalletLogPrintf("Saving scan progress %d.\n", block_height);
+ WalletBatch batch(GetDatabase());
+ batch.WriteBestBlock(loc);
+ }
+ }
} else {
// could not scan block, keep scanning but record this block as the most recent failure
result.last_failed_block = block_hash;
@@ -1788,6 +1801,10 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
}
}
}
+ if (!max_height) {
+ WalletLogPrintf("Scanning current mempool transactions.\n");
+ WITH_LOCK(cs_wallet, chain().requestMempoolTransactions(*this));
+ }
ShowProgress(strprintf("%s " + _("Rescanning…").translated, GetDisplayName()), 100); // hide progress dialog in GUI
if (block_height && fAbortRescan) {
WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", block_height, progress_current);
@@ -1796,8 +1813,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const uint256& start_bloc
WalletLogPrintf("Rescan interrupted by shutdown request at block %d. Progress=%f\n", block_height, progress_current);
result.status = ScanResult::USER_ABORT;
} else {
- auto duration_milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(Clock::now() - start_time);
- WalletLogPrintf("Rescan completed in %15dms\n", duration_milliseconds.count());
+ WalletLogPrintf("Rescan completed in %15dms\n", Ticks<std::chrono::milliseconds>(reserver.now() - start_time));
}
return result;
}
@@ -1969,7 +1985,6 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
if (n_signed) {
*n_signed = 0;
}
- const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
LOCK(cs_wallet);
// Get all of the previous transactions
for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) {
@@ -1993,6 +2008,8 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
}
+ const PrecomputedTransactionData txdata = PrecomputePSBTData(psbtx);
+
// Fill in information from ScriptPubKeyMans
for (ScriptPubKeyMan* spk_man : GetAllScriptPubKeyMans()) {
int n_signed_this_spkm = 0;
@@ -2307,37 +2324,36 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
return res;
}
-bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error)
+BResult<CTxDestination> CWallet::GetNewDestination(const OutputType type, const std::string label)
{
LOCK(cs_wallet);
- error.clear();
- bool result = false;
auto spk_man = GetScriptPubKeyMan(type, false /* internal */);
- if (spk_man) {
- spk_man->TopUp();
- result = spk_man->GetNewDestination(type, dest, error);
- } else {
- error = strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
+ if (!spk_man) {
+ return strprintf(_("Error: No %s addresses available."), FormatOutputType(type));
}
- if (result) {
- SetAddressBook(dest, label, "receive");
+
+ spk_man->TopUp();
+ auto op_dest = spk_man->GetNewDestination(type);
+ if (op_dest) {
+ SetAddressBook(op_dest.GetObj(), label, "receive");
}
- return result;
+ return op_dest;
}
-bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error)
+BResult<CTxDestination> CWallet::GetNewChangeDestination(const OutputType type)
{
LOCK(cs_wallet);
- error.clear();
+ CTxDestination dest;
+ bilingual_str error;
ReserveDestination reservedest(this, type);
if (!reservedest.GetReservedDestination(dest, true, error)) {
- return false;
+ return error;
}
reservedest.KeepDestination();
- return true;
+ return dest;
}
std::optional<int64_t> CWallet::GetOldestKeyPoolTime() const
@@ -2368,21 +2384,45 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations
}
}
-std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
+void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const
{
AssertLockHeld(cs_wallet);
- std::set<CTxDestination> result;
- for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book)
- {
- if (item.second.IsChange()) continue;
- const CTxDestination& address = item.first;
- const std::string& strName = item.second.GetLabel();
- if (strName == label)
- result.insert(address);
+ for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book) {
+ const auto& entry = item.second;
+ func(item.first, entry.GetLabel(), entry.purpose, entry.IsChange());
}
+}
+
+std::vector<CTxDestination> CWallet::ListAddrBookAddresses(const std::optional<AddrBookFilter>& _filter) const
+{
+ AssertLockHeld(cs_wallet);
+ std::vector<CTxDestination> result;
+ AddrBookFilter filter = _filter ? *_filter : AddrBookFilter();
+ ForEachAddrBookEntry([&result, &filter](const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change) {
+ // Filter by change
+ if (filter.ignore_change && is_change) return;
+ // Filter by label
+ if (filter.m_op_label && *filter.m_op_label != label) return;
+ // All good
+ result.emplace_back(dest);
+ });
return result;
}
+std::set<std::string> CWallet::ListAddrBookLabels(const std::string& purpose) const
+{
+ AssertLockHeld(cs_wallet);
+ std::set<std::string> label_set;
+ ForEachAddrBookEntry([&](const CTxDestination& _dest, const std::string& _label,
+ const std::string& _purpose, bool _is_change) {
+ if (_is_change) return;
+ if (purpose.empty() || _purpose == purpose) {
+ label_set.insert(_label);
+ }
+ });
+ return label_set;
+}
+
bool ReserveDestination::GetReservedDestination(CTxDestination& dest, bool internal, bilingual_str& error)
{
m_spk_man = pwallet->GetScriptPubKeyMan(type, internal);
@@ -3011,20 +3051,31 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
if (tip_height && *tip_height != rescan_height)
{
- if (chain.havePruned()) {
+ // Technically we could execute the code below in any case, but performing the
+ // `while` loop below can make startup very slow, so only check blocks on disk
+ // if necessary.
+ if (chain.havePruned() || chain.hasAssumedValidChain()) {
int block_height = *tip_height;
while (block_height > 0 && chain.haveBlockOnDisk(block_height - 1) && rescan_height != block_height) {
--block_height;
}
if (rescan_height != block_height) {
- // We can't rescan beyond non-pruned blocks, stop and throw an error.
+ // We can't rescan beyond blocks we don't have data for, 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
// Exit early and print an error.
+ // It also may happen if an assumed-valid chain is in use and therefore not
+ // all block data is available.
// If a block is pruned after this check, we will load the wallet,
// but fail the rescan with a generic error.
- error = _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
+
+ error = chain.hasAssumedValidChain() ?
+ _(
+ "Assumed-valid: last wallet synchronisation goes beyond "
+ "available block data. You need to wait for the background "
+ "validation chain to download more blocks.") :
+ _("Prune: last wallet synchronisation goes beyond pruned data. You need to -reindex (download the whole blockchain again in case of pruned node)");
return false;
}
}
@@ -3045,7 +3096,7 @@ bool CWallet::AttachChain(const std::shared_ptr<CWallet>& walletInstance, interf
{
WalletRescanReserver reserver(*walletInstance);
- if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, {} /* max height */, reserver, true /* update */).status)) {
+ if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance->ScanForWalletTransactions(chain.getBlockHash(rescan_height), rescan_height, /*max_height=*/{}, reserver, /*fUpdate=*/true, /*save_progress=*/true).status)) {
error = _("Failed to rescan the wallet during initialization");
return false;
}
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index bf42b3da9e..15f5917040 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -15,6 +15,7 @@
#include <psbt.h>
#include <tinyformat.h>
#include <util/message.h>
+#include <util/result.h>
#include <util/strencodings.h>
#include <util/string.h>
#include <util/system.h>
@@ -45,7 +46,6 @@ using LoadWalletFn = std::function<void(std::unique_ptr<interfaces::Wallet> wall
class CScript;
enum class FeeEstimateMode;
-struct FeeCalculation;
struct bilingual_str;
namespace wallet {
@@ -508,8 +508,8 @@ public:
CWalletTx* AddToWallet(CTransactionRef tx, const TxState& state, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true, bool rescanning_old_block = false);
bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override;
- void blockConnected(const CBlock& block, int height) override;
- void blockDisconnected(const CBlock& block, int height) override;
+ void blockConnected(const interfaces::BlockInfo& block) override;
+ void blockDisconnected(const interfaces::BlockInfo& block) override;
void updatedBlockTip() override;
int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver& reserver, bool update);
@@ -528,7 +528,7 @@ public:
//! USER_ABORT.
uint256 last_failed_block;
};
- ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate);
+ ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, std::optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate, const bool save_progress);
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions();
@@ -635,7 +635,30 @@ public:
std::optional<int64_t> GetOldestKeyPoolTime() const;
- std::set<CTxDestination> GetLabelAddresses(const std::string& label) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ // Filter struct for 'ListAddrBookAddresses'
+ struct AddrBookFilter {
+ // Fetch addresses with the provided label
+ std::optional<std::string> m_op_label{std::nullopt};
+ // Don't include change addresses by default
+ bool ignore_change{true};
+ };
+
+ /**
+ * Filter and retrieve destinations stored in the addressbook
+ */
+ std::vector<CTxDestination> ListAddrBookAddresses(const std::optional<AddrBookFilter>& filter) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /**
+ * Retrieve all the known labels in the address book
+ */
+ std::set<std::string> ListAddrBookLabels(const std::string& purpose) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+
+ /**
+ * Walk-through the address book entries.
+ * Stops when the provided 'ListAddrBookFunc' returns false.
+ */
+ using ListAddrBookFunc = std::function<void(const CTxDestination& dest, const std::string& label, const std::string& purpose, bool is_change)>;
+ void ForEachAddrBookEntry(const ListAddrBookFunc& func) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Marks all outputs in each one of the destinations dirty, so their cache is
@@ -643,8 +666,8 @@ public:
*/
void MarkDestinationsDirty(const std::set<CTxDestination>& destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
- bool GetNewDestination(const OutputType type, const std::string label, CTxDestination& dest, bilingual_str& error);
- bool GetNewChangeDestination(const OutputType type, CTxDestination& dest, bilingual_str& error);
+ BResult<CTxDestination> GetNewDestination(const OutputType type, const std::string label);
+ BResult<CTxDestination> GetNewChangeDestination(const OutputType type);
isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -896,8 +919,11 @@ void MaybeResendWalletTxs(WalletContext& context);
class WalletRescanReserver
{
private:
+ using Clock = std::chrono::steady_clock;
+ using NowFn = std::function<Clock::time_point()>;
CWallet& m_wallet;
bool m_could_reserve;
+ NowFn m_now;
public:
explicit WalletRescanReserver(CWallet& w) : m_wallet(w), m_could_reserve(false) {}
@@ -918,6 +944,10 @@ public:
return (m_could_reserve && m_wallet.fScanningWallet);
}
+ Clock::time_point now() const { return m_now ? m_now() : Clock::now(); };
+
+ void setNow(NowFn now) { m_now = std::move(now); }
+
~WalletRescanReserver()
{
if (m_could_reserve) {
@@ -932,7 +962,7 @@ bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
//! Remove wallet name from persistent configuration so it will not be loaded on startup.
bool RemoveWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
-bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, bool use_max_sig);
+bool DummySignInput(const SigningProvider& provider, CTxIn &tx_in, const CTxOut &txout, const CCoinControl* coin_control = nullptr);
bool FillInputToWeight(CTxIn& txin, int64_t target_weight);
} // namespace wallet