Skip to content

Commit e276b68

Browse files
committed
wallet: Avoid use of Chain::Lock in listsinceblock
This is a step toward removing the Chain::Lock class and reducing cs_main locking. This change only affects behavior in the case where wallet last block processed falls behind the chain tip. Previously listsinceblock might not have returned all transactions up to the claimed "lastblock" value in this case, resulting in race conditions and potentially missing transactions in cases where listsinceblock was called in a loop like bitcoin#14338 (comment)
1 parent b91020a commit e276b68

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

src/interfaces/chain.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ class ChainImpl : public Chain
259259
}
260260
return true;
261261
}
262+
uint256 findAncestorByHeight(const uint256& block_hash, int ancestor_height) override
263+
{
264+
LOCK(::cs_main);
265+
if (const CBlockIndex* block = LookupBlockIndex(block_hash)) {
266+
const CBlockIndex* ancestor = block->GetAncestor(ancestor_height);
267+
if (ancestor) return ancestor->GetBlockHash();
268+
}
269+
return {};
270+
}
262271
bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, int* height) override
263272
{
264273
LOCK(::cs_main);
@@ -268,6 +277,22 @@ class ChainImpl : public Chain
268277
if (height) *height = ancestor->nHeight;
269278
return true;
270279
}
280+
Optional<int> findCommonAncestor(const uint256& block_hash1,
281+
const uint256& block_hash2,
282+
uint256* ancestor_hash,
283+
int* ancestor_height) override
284+
{
285+
LOCK(::cs_main);
286+
const CBlockIndex* block1 = LookupBlockIndex(block_hash1);
287+
const CBlockIndex* block2 = LookupBlockIndex(block_hash2);
288+
if (block1 && block2) {
289+
const CBlockIndex* ancestor = LastCommonAncestor(block1, block2);
290+
if (ancestor_hash) *ancestor_hash = ancestor->GetBlockHash();
291+
if (ancestor_height) *ancestor_height = ancestor->nHeight;
292+
return block1->nHeight;
293+
}
294+
return nullopt;
295+
}
271296
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
272297
double guessVerificationProgress(const uint256& block_hash) override
273298
{

src/interfaces/chain.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,23 @@ class Chain
133133
int64_t* max_time = nullptr,
134134
int64_t* mtp_time = nullptr) = 0;
135135

136+
//! Find ancestor of block at specified height and return its hash.
137+
virtual uint256 findAncestorByHeight(const uint256& block_hash, int ancestor_height) = 0;
138+
136139
//! Return whether block descends from a specified ancestor, and
137140
//! optionally return height of the ancestor.
138141
virtual bool findAncestorByHash(const uint256& block_hash,
139142
const uint256& ancestor_hash,
140143
int* height = 0) = 0;
141144

145+
//! Find most recent common ancestor between two blocks and optionally
146+
//! return its hash and/or height. Also return height of first block. Return
147+
//! nullopt if either block is unknown or there is no common ancestor.
148+
virtual Optional<int> findCommonAncestor(const uint256& block_hash1,
149+
const uint256& block_hash2,
150+
uint256* ancestor_hash,
151+
int* ancestor_height) = 0;
152+
142153
//! Look up unspent output information. Returns coins in the mempool and in
143154
//! the current chain UTXO set. Iterates through all the keys in the map and
144155
//! populates the values.

src/test/interfaces_tests.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

5+
#include <chainparams.h>
6+
#include <consensus/validation.h>
57
#include <interfaces/chain.h>
8+
#include <script/standard.h>
69
#include <test/util/setup_common.h>
710
#include <validation.h>
811

@@ -19,6 +22,14 @@ BOOST_AUTO_TEST_CASE(findBlock)
1922
BOOST_CHECK_EQUAL(time_mtp, active[20]->GetMedianTimePast());
2023
}
2124

25+
BOOST_AUTO_TEST_CASE(findAncestorByHeight)
26+
{
27+
auto chain = interfaces::MakeChain(m_node);
28+
auto& active = ChainActive();
29+
BOOST_CHECK_EQUAL(chain->findAncestorByHeight(active[20]->GetBlockHash(), 10), active[10]->GetBlockHash());
30+
BOOST_CHECK_EQUAL(chain->findAncestorByHeight(active[10]->GetBlockHash(), 20), uint256());
31+
}
32+
2233
BOOST_AUTO_TEST_CASE(findAncestorByHash)
2334
{
2435
auto chain = interfaces::MakeChain(m_node);
@@ -29,4 +40,26 @@ BOOST_AUTO_TEST_CASE(findAncestorByHash)
2940
BOOST_CHECK(!chain->findAncestorByHash(active[10]->GetBlockHash(), active[20]->GetBlockHash()));
3041
}
3142

43+
BOOST_AUTO_TEST_CASE(findCommonAncestor)
44+
{
45+
auto chain = interfaces::MakeChain(m_node);
46+
auto& active = ChainActive();
47+
auto* orig_tip = active.Tip();
48+
for (int i = 0; i < 10; ++i) {
49+
BlockValidationState state;
50+
ChainstateActive().InvalidateBlock(state, Params(), active.Tip());
51+
}
52+
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10);
53+
coinbaseKey.MakeNewKey(true);
54+
for (int i = 0; i < 20; ++i) {
55+
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
56+
}
57+
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight + 10);
58+
uint256 fork_hash;
59+
int fork_height;
60+
BOOST_CHECK_EQUAL(*chain->findCommonAncestor(orig_tip->GetBlockHash(), active.Tip()->GetBlockHash(), &fork_hash, &fork_height), orig_tip->nHeight);
61+
BOOST_CHECK_EQUAL(fork_height, orig_tip->nHeight - 10);
62+
BOOST_CHECK_EQUAL(fork_hash, active[fork_height]->GetBlockHash());
63+
}
64+
3265
BOOST_AUTO_TEST_SUITE_END()

src/wallet/rpcwallet.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,8 +1585,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
15851585
uint256 blockId;
15861586
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
15871587
blockId = ParseHashV(request.params[0], "blockhash");
1588-
height = locked_chain->findFork(blockId, &altheight);
1589-
if (!height) {
1588+
height.emplace();
1589+
altheight = pwallet->chain().findCommonAncestor(blockId, pwallet->GetLastBlockHash(), nullptr, height.get_ptr());
1590+
if (!altheight) {
15901591
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
15911592
}
15921593
}
@@ -1605,8 +1606,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16051606

16061607
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
16071608

1608-
const Optional<int> tip_height = locked_chain->getHeight();
1609-
int depth = tip_height && height ? (1 + *tip_height - *height) : -1;
1609+
int depth = height ? pwallet->GetLastBlockHeight() + 1 - *height : -1;
16101610

16111611
UniValue transactions(UniValue::VARR);
16121612

@@ -1638,8 +1638,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16381638
--*altheight;
16391639
}
16401640

1641-
int last_height = tip_height ? *tip_height + 1 - target_confirms : -1;
1642-
uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256();
1641+
uint256 lastblock = pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), pwallet->GetLastBlockHeight() + 1 - target_confirms);
16431642

16441643
UniValue ret(UniValue::VOBJ);
16451644
ret.pushKV("transactions", transactions);

0 commit comments

Comments
 (0)