aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/bdb.cpp6
-rw-r--r--src/wallet/coinselection.cpp3
-rw-r--r--src/wallet/interfaces.cpp20
-rw-r--r--src/wallet/rpc/addresses.cpp33
-rw-r--r--src/wallet/rpc/coins.cpp6
-rw-r--r--src/wallet/rpc/transactions.cpp67
-rw-r--r--src/wallet/scriptpubkeyman.cpp13
-rw-r--r--src/wallet/spend.cpp38
-rw-r--r--src/wallet/spend.h17
-rw-r--r--src/wallet/test/coinselector_tests.cpp10
-rw-r--r--src/wallet/wallet.cpp85
-rw-r--r--src/wallet/wallet.h27
-rw-r--r--src/wallet/walletdb.cpp41
-rw-r--r--src/wallet/walletdb.h1
14 files changed, 214 insertions, 153 deletions
diff --git a/src/wallet/bdb.cpp b/src/wallet/bdb.cpp
index f8230f7a1d..dbd768a758 100644
--- a/src/wallet/bdb.cpp
+++ b/src/wallet/bdb.cpp
@@ -315,12 +315,6 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, b
env = database.env.get();
pdb = database.m_db.get();
strFile = fs::PathToString(database.m_filename);
- if (!Exists(std::string("version"))) {
- bool fTmp = fReadOnly;
- fReadOnly = false;
- Write(std::string("version"), CLIENT_VERSION);
- fReadOnly = fTmp;
- }
}
void BerkeleyDatabase::Open()
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index 07df8d9fc8..49e6bac462 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -104,9 +104,6 @@ std::optional<SelectionResult> SelectCoinsBnB(std::vector<OutputGroup>& utxo_poo
if (curr_waste <= best_waste) {
best_selection = curr_selection;
best_waste = curr_waste;
- if (best_waste == 0) {
- break;
- }
}
curr_waste -= (curr_value - selection_target); // Remove the excess value as we will be selecting different coins now
backtrack = true;
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index f54b2c83d2..327276f2ed 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -191,29 +191,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 {
diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
index f25ad59528..da4cc44ee6 100644
--- a/src/wallet/rpc/addresses.cpp
+++ b/src/wallet/rpc/addresses.cpp
@@ -637,17 +637,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 +669,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 +682,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 +733,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/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/transactions.cpp b/src/wallet/rpc/transactions.cpp
index fae9bf3ea5..6e42c0736d 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);
diff --git a/src/wallet/scriptpubkeyman.cpp b/src/wallet/scriptpubkeyman.cpp
index 8633e7c62c..1fec82a485 100644
--- a/src/wallet/scriptpubkeyman.cpp
+++ b/src/wallet/scriptpubkeyman.cpp
@@ -2180,6 +2180,19 @@ TransactionError DescriptorScriptPubKeyMan::FillPSBT(PartiallySignedTransaction&
*keys = Merge(*keys, *pk_keys);
}
}
+ for (const auto& pk_pair : input.m_tap_bip32_paths) {
+ const XOnlyPubKey& pubkey = pk_pair.first;
+ for (unsigned char prefix : {0x02, 0x03}) {
+ unsigned char b[33] = {prefix};
+ std::copy(pubkey.begin(), pubkey.end(), b + 1);
+ CPubKey fullpubkey;
+ fullpubkey.Set(b, b + 33);
+ std::unique_ptr<FlatSigningProvider> pk_keys = GetSigningProvider(fullpubkey);
+ if (pk_keys) {
+ *keys = Merge(*keys, *pk_keys);
+ }
+ }
+ }
}
SignPSBTInput(HidingSigningProvider(keys.get(), !sign, !bip32derivs), psbtx, i, &txdata, sighash_type, nullptr, finalize);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 5799a9ff2a..21007f5a92 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)) {
@@ -760,7 +759,8 @@ static std::optional<CreatedTransactionResult> CreateTransactionInternal(
// vouts to the payees
if (!coin_selection_params.m_subtract_fee_outputs) {
- coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size)
+ coin_selection_params.tx_noinputs_size = 10; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 witness overhead (dummy, flag, stack size)
+ coin_selection_params.tx_noinputs_size += GetSizeOfCompactSize(vecSend.size()); // bytes for output count
}
for (const auto& recipient : vecSend)
{
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index cba42d6fae..21f0095e77 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -14,23 +14,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);
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index d6f47e9954..85f3cd7f3c 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -86,7 +86,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.
@@ -198,8 +199,8 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
expected_result.Clear();
// Select 5 Cent
- add_coin(4 * CENT, 4, expected_result);
- add_coin(1 * CENT, 1, expected_result);
+ add_coin(3 * CENT, 3, expected_result);
+ add_coin(2 * CENT, 2, expected_result);
const auto result3 = SelectCoinsBnB(GroupCoins(utxo_pool), 5 * CENT, 0.5 * CENT);
BOOST_CHECK(result3);
BOOST_CHECK(EquivalentResult(expected_result, *result3));
@@ -224,8 +225,9 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
// Select 10 Cent
add_coin(5 * CENT, 5, utxo_pool);
- add_coin(5 * CENT, 5, expected_result);
add_coin(4 * CENT, 4, expected_result);
+ add_coin(3 * CENT, 3, expected_result);
+ add_coin(2 * CENT, 2, expected_result);
add_coin(1 * CENT, 1, expected_result);
const auto result5 = SelectCoinsBnB(GroupCoins(utxo_pool), 10 * CENT, 0.5 * CENT);
BOOST_CHECK(result5);
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index d0b093bbb7..3349b119cc 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1504,13 +1504,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 +1580,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;
}
}
@@ -2006,6 +2006,35 @@ TransactionError CWallet::FillPSBT(PartiallySignedTransaction& psbtx, bool& comp
}
}
+ // Only drop non_witness_utxos if sighash_type != SIGHASH_ANYONECANPAY
+ if ((sighash_type & 0x80) != SIGHASH_ANYONECANPAY) {
+ // Figure out if any non_witness_utxos should be dropped
+ std::vector<unsigned int> to_drop;
+ for (unsigned int i = 0; i < psbtx.inputs.size(); ++i) {
+ const auto& input = psbtx.inputs.at(i);
+ int wit_ver;
+ std::vector<unsigned char> wit_prog;
+ if (input.witness_utxo.IsNull() || !input.witness_utxo.scriptPubKey.IsWitnessProgram(wit_ver, wit_prog)) {
+ // There's a non-segwit input or Segwit v0, so we cannot drop any witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (wit_ver == 0) {
+ // Segwit v0, so we cannot drop any non_witness_utxos
+ to_drop.clear();
+ break;
+ }
+ if (input.non_witness_utxo) {
+ to_drop.push_back(i);
+ }
+ }
+
+ // Drop the non_witness_utxos that we can drop
+ for (unsigned int i : to_drop) {
+ psbtx.inputs.at(i).non_witness_utxo = nullptr;
+ }
+ }
+
// Complete if every input is now signed
complete = true;
for (const auto& input : psbtx.inputs) {
@@ -2339,21 +2368,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);
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index bf42b3da9e..669aa8d195 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -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
@@ -932,7 +955,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
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 79e0a330b7..8afd3f416d 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -883,12 +883,10 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
if (result != DBErrors::LOAD_OK)
return result;
- // Last client version to open this wallet, was previously the file version number
+ // Last client version to open this wallet
int last_client = CLIENT_VERSION;
- m_batch->Read(DBKeys::VERSION, last_client);
-
- int wallet_version = pwallet->GetVersion();
- pwallet->WalletLogPrintf("Wallet File Version = %d\n", wallet_version > 0 ? wallet_version : last_client);
+ bool has_last_client = m_batch->Read(DBKeys::VERSION, last_client);
+ pwallet->WalletLogPrintf("Wallet file version = %d, last client version = %d\n", pwallet->GetVersion(), last_client);
pwallet->WalletLogPrintf("Keys: %u plaintext, %u encrypted, %u w/ metadata, %u total. Unknown wallet records: %u\n",
wss.nKeys, wss.nCKeys, wss.nKeyMeta, wss.nKeys + wss.nCKeys, wss.m_unknown_records);
@@ -909,7 +907,7 @@ DBErrors WalletBatch::LoadWallet(CWallet* pwallet)
if (wss.fIsEncrypted && (last_client == 40000 || last_client == 50000))
return DBErrors::NEED_REWRITE;
- if (last_client < CLIENT_VERSION) // Update
+ if (!has_last_client || last_client != CLIENT_VERSION) // Update
m_batch->Write(DBKeys::VERSION, CLIENT_VERSION);
if (wss.fAnyUnordered)
@@ -1186,13 +1184,36 @@ std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase()
}
/** Return object for accessing temporary in-memory database. */
-std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
+std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options)
{
- DatabaseOptions options;
+
+ std::optional<DatabaseFormat> format;
+ if (options.require_format) format = options.require_format;
+ if (!format) {
+#ifdef USE_BDB
+ format = DatabaseFormat::BERKELEY;
+#endif
+#ifdef USE_SQLITE
+ format = DatabaseFormat::SQLITE;
+#endif
+ }
+
+ if (format == DatabaseFormat::SQLITE) {
#ifdef USE_SQLITE
- return std::make_unique<SQLiteDatabase>("", "", options, true);
-#elif USE_BDB
+ return std::make_unique<SQLiteDatabase>(":memory:", "", options, true);
+#endif
+ assert(false);
+ }
+
+#ifdef USE_BDB
return std::make_unique<BerkeleyDatabase>(std::make_shared<BerkeleyEnvironment>(), "", options);
#endif
+ assert(false);
+}
+
+std::unique_ptr<WalletDatabase> CreateMockWalletDatabase()
+{
+ DatabaseOptions options;
+ return CreateMockWalletDatabase(options);
}
} // namespace wallet
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 3dfe781d56..a04ea598b6 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -301,6 +301,7 @@ bool ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, st
std::unique_ptr<WalletDatabase> CreateDummyWalletDatabase();
/** Return object for accessing temporary in-memory database. */
+std::unique_ptr<WalletDatabase> CreateMockWalletDatabase(DatabaseOptions& options);
std::unique_ptr<WalletDatabase> CreateMockWalletDatabase();
} // namespace wallet