Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

backport: bitcoin#25383, 25471, 25535, 19393, 25337 #6524

Draft
wants to merge 5 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Wallet
std::string* purpose) = 0;

//! Get wallet address list.
virtual std::vector<WalletAddress> getAddresses() = 0;
virtual std::vector<WalletAddress> getAddresses() const = 0;

//! Get receive requests.
virtual std::vector<std::string> getAddressReceiveRequests() = 0;
Expand Down
6 changes: 5 additions & 1 deletion src/rpc/blockchain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,7 @@ static RPCHelpMan gettxoutsetinfo()
"Note this call may take some time if you are not using coinstatsindex.\n",
{
{"hash_type", RPCArg::Type::STR, RPCArg::Default{"hash_serialized_2"}, "Which UTXO set hash should be calculated. Options: 'hash_serialized_2' (the legacy algorithm), 'muhash', 'none'."},
{"hash_or_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}},
{"hash_or_height", RPCArg::Type::NUM, RPCArg::DefaultHint{"the current best block"}, "The block hash or height of the target height (only available with coinstatsindex).", "", {"", "string or numeric"}},
{"use_index", RPCArg::Type::BOOL, RPCArg::Default{true}, "Use coinstatsindex, if available."},
},
RPCResult{
Expand Down Expand Up @@ -1077,6 +1077,7 @@ static RPCHelpMan gettxoutsetinfo()
HelpExampleCli("gettxoutsetinfo", R"("none")") +
HelpExampleCli("gettxoutsetinfo", R"("none" 1000)") +
HelpExampleCli("gettxoutsetinfo", R"("none" '"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09"')") +
HelpExampleCli("-named gettxoutsetinfo", R"(hash_type='muhash' use_index='false')") +
HelpExampleRpc("gettxoutsetinfo", "") +
HelpExampleRpc("gettxoutsetinfo", R"("none")") +
HelpExampleRpc("gettxoutsetinfo", R"("none", 1000)") +
Expand Down Expand Up @@ -1114,6 +1115,9 @@ static RPCHelpMan gettxoutsetinfo()
throw JSONRPCError(RPC_INVALID_PARAMETER, "hash_serialized_2 hash type cannot be queried for a specific block");
}

if (!index_requested) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot set use_index to false when querying for a specific block");
}
pindex = ParseHashOrHeight(request.params[1], chainman);
}

Expand Down
6 changes: 0 additions & 6 deletions src/wallet/bdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,6 @@ BerkeleyBatch::BerkeleyBatch(BerkeleyDatabase& database, const bool read_only, b
env = database.env.get();
pdb = database.m_db.get();
strFile = database.strFile;
if (!Exists(std::string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
Write(std::string("version"), CLIENT_VERSION);
fReadOnly = fTmp;
}
}

void BerkeleyDatabase::Open()
Expand Down
20 changes: 9 additions & 11 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,29 +204,27 @@ class WalletImpl : public Wallet
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 {
Expand Down
56 changes: 47 additions & 9 deletions src/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4388,21 +4388,59 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
return ret;
}

std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const
void CWallet::ForEachAddrBookEntry(const ListAddrBookFunc& func) const
{
LOCK(cs_wallet);
std::set<CTxDestination> result;
for (const std::pair<const CTxDestination, CAddressBookData>& item : m_address_book)
{
if (item.second.IsChange()) continue;
const CTxDestination& address = item.first;
const std::string& strName = item.second.GetLabel();
if (strName == label)
result.insert(address);
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);
});
}

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 fInternalIn)
{
m_spk_man = pwallet->GetScriptPubKeyMan(fInternalIn);
Expand Down
25 changes: 24 additions & 1 deletion src/wallet/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,30 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati
std::set<std::set<CTxDestination>> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::map<CTxDestination, CAmount> GetAddressBalances() const;

std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
// 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
Expand Down
10 changes: 4 additions & 6 deletions src/wallet/walletdb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -862,12 +862,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 total; Watch scripts: %u; HD PubKeys: %u; Metadata: %u; Unknown wallet records: %u\n",
wss.nKeys, wss.nCKeys, wss.nKeys + wss.nCKeys,
Expand All @@ -889,7 +887,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)
Expand Down
10 changes: 7 additions & 3 deletions test/functional/feature_dbcrash.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def set_test_params(self):
self.node2_args = ["-dbcrashratio=24", "-dbcache=16"] + self.base_args

# Node3 is a normal node with default args, will mine full blocks
# and non-standard txs (e.g. txs with "dust" outputs)
self.node3_args = ["-acceptnonstdtxn"]
# and txs (with "dust" outputs
self.node3_args = ["-dustrelayfee=0"]
self.extra_args = [self.node0_args, self.node1_args, self.node2_args, self.node3_args]

def skip_test_if_missing_module(self):
Expand Down Expand Up @@ -217,7 +217,11 @@ def run_test(self):

# Start by creating a lot of utxos on node3
initial_height = self.nodes[3].getblockcount()
utxo_list = create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 5000, sync_fun=self.no_op)
utxo_list = []
for _ in range(5):
utxo_list.extend(create_confirmed_utxos(self, self.nodes[3].getnetworkinfo()['relayfee'], self.nodes[3], 1000, sync_fun=self.no_op))
self.generate(self.nodes[3], 1, sync_fun=self.no_op)
assert_equal(len(self.nodes[3].getrawmempool()), 0)
self.log.info(f"Prepped {len(utxo_list)} utxo entries")

# Sync these blocks with the other nodes
Expand Down
68 changes: 63 additions & 5 deletions test/functional/p2p_invalid_tx.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ def run_test(self):
block.solve()
# Save the coinbase for later
block2 = block
tip = block.sha256
node.p2ps[0].send_blocks_and_test([block1, block2], node, success=True)

self.log.info("Mature the block.")
Expand Down Expand Up @@ -116,24 +115,24 @@ def test_orphan_tx_handling(self, base_tx, resolve_via_block):
SCRIPT_PUB_KEY_OP_TRUE = b'\x51\x75' * 15 + b'\x51'
tx_withhold = CTransaction()
tx_withhold.vin.append(CTxIn(outpoint=COutPoint(base_tx, 0)))
tx_withhold.vout.append(CTxOut(nValue=50 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_withhold.vout = [(CTxOut(nValue=25 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
tx_withhold.calc_sha256()

# Our first orphan tx with some outputs to create further orphan txs
tx_orphan_1 = CTransaction()
tx_orphan_1.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 0)))
tx_orphan_1.vout = [CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
tx_orphan_1.vout = [CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 3
tx_orphan_1.calc_sha256()

# A valid transaction with low fee
tx_orphan_2_no_fee = CTransaction()
tx_orphan_2_no_fee.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 0)))
tx_orphan_2_no_fee.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_2_no_fee.vout.append(CTxOut(nValue=8 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))

# A valid transaction with sufficient fee
tx_orphan_2_valid = CTransaction()
tx_orphan_2_valid.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_1.sha256, 1)))
tx_orphan_2_valid.vout.append(CTxOut(nValue=10 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_2_valid.vout.append(CTxOut(nValue=8 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_2_valid.calc_sha256()

# An invalid transaction with negative fee
Expand Down Expand Up @@ -197,6 +196,7 @@ def test_orphan_tx_handling(self, base_tx, resolve_via_block):
with node.assert_debug_log(['orphanage overflow, removed 1 tx']):
node.p2ps[0].send_txs_and_test(orphan_tx_pool, node, success=False)

self.log.info('Test orphan with rejected parents')
rejected_parent = CTransaction()
rejected_parent.vin.append(CTxIn(outpoint=COutPoint(tx_orphan_2_invalid.sha256, 0)))
rejected_parent.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
Expand All @@ -205,6 +205,64 @@ def test_orphan_tx_handling(self, base_tx, resolve_via_block):
#with node.assert_debug_log(['not keeping orphan with rejected parents {}'.format(rejected_parent.hash)]):
node.p2ps[0].send_txs_and_test([rejected_parent], node, success=False)

self.log.info('Test that a peer disconnection causes erase its transactions from the orphan pool')
with node.assert_debug_log(['Erased 100 orphan tx from peer=25']):
self.reconnect_p2p(num_connections=1)

self.log.info('Test that a transaction in the orphan pool is included in a new tip block causes erase this transaction from the orphan pool')
tx_withhold_until_block_A = CTransaction()
tx_withhold_until_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold.sha256, 1)))
tx_withhold_until_block_A.vout = [CTxOut(nValue=12 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE)] * 2
tx_withhold_until_block_A.calc_sha256()

tx_orphan_include_by_block_A = CTransaction()
tx_orphan_include_by_block_A.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 0)))
tx_orphan_include_by_block_A.vout.append(CTxOut(nValue=12 * COIN - 12000, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_include_by_block_A.calc_sha256()

self.log.info('Send the orphan ... ')
node.p2ps[0].send_txs_and_test([tx_orphan_include_by_block_A], node, success=False)

tip = int(node.getbestblockhash(), 16)
height = node.getblockcount() + 1
block_A = create_block(tip, create_coinbase(height))
block_A.vtx.extend([tx_withhold, tx_withhold_until_block_A, tx_orphan_include_by_block_A])
block_A.hashMerkleRoot = block_A.calc_merkle_root()
block_A.solve()

self.log.info('Send the block that includes the previous orphan ... ')
with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
node.p2ps[0].send_blocks_and_test([block_A], node, success=True)

self.log.info('Test that a transaction in the orphan pool conflicts with a new tip block causes erase this transaction from the orphan pool')
tx_withhold_until_block_B = CTransaction()
tx_withhold_until_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_A.sha256, 1)))
tx_withhold_until_block_B.vout.append(CTxOut(nValue=11 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_withhold_until_block_B.calc_sha256()

tx_orphan_include_by_block_B = CTransaction()
tx_orphan_include_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
tx_orphan_include_by_block_B.vout.append(CTxOut(nValue=10 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_include_by_block_B.calc_sha256()

tx_orphan_conflict_by_block_B = CTransaction()
tx_orphan_conflict_by_block_B.vin.append(CTxIn(outpoint=COutPoint(tx_withhold_until_block_B.sha256, 0)))
tx_orphan_conflict_by_block_B.vout.append(CTxOut(nValue=9 * COIN, scriptPubKey=SCRIPT_PUB_KEY_OP_TRUE))
tx_orphan_conflict_by_block_B.calc_sha256()
self.log.info('Send the orphan ... ')
node.p2ps[0].send_txs_and_test([tx_orphan_conflict_by_block_B], node, success=False)

tip = int(node.getbestblockhash(), 16)
height = node.getblockcount() + 1
block_B = create_block(tip, create_coinbase(height))
block_B.vtx.extend([tx_withhold_until_block_B, tx_orphan_include_by_block_B])
block_B.hashMerkleRoot = block_B.calc_merkle_root()
block_B.solve()

self.log.info('Send the block that includes a transaction which conflicts with the previous orphan ... ')
with node.assert_debug_log(["Erased 1 orphan tx included or conflicted by block"]):
node.p2ps[0].send_blocks_and_test([block_B], node, success=True)


if __name__ == '__main__':
InvalidTxRequestTest().main()
4 changes: 2 additions & 2 deletions test/functional/wallet_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ def set_test_params(self):
self.num_nodes = 4
if self.options.descriptors:
self.extra_args = [[
"-acceptnonstdtxn=1"
"-dustrelayfee=0"
] for i in range(self.num_nodes)]
else:
self.extra_args = [[
"-acceptnonstdtxn=1",
"-dustrelayfee=0",
'-usehd={:d}'.format(i%2==0)
] for i in range(self.num_nodes)]
self.setup_clean_chain = True
Expand Down
5 changes: 5 additions & 0 deletions test/functional/wallet_listreceivedby.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ def run_test(self):
{"address": empty_addr},
{"address": empty_addr, "label": "", "amount": 0, "confirmations": 0, "txids": []})

# No returned addy should be a change addr
for node in self.nodes:
for addr_obj in node.listreceivedbyaddress():
assert_equal(node.getaddressinfo(addr_obj["address"])["ischange"], False)

# Test Address filtering
# Only on addr
expected = {"address": addr, "label": "", "amount": Decimal("0.1"), "confirmations": 10, "txids": [txid, ]}
Expand Down
Loading