Skip to content

Commit f7ba881

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/bitcoin#14338 (comment)
1 parent bc96a9b commit f7ba881

File tree

4 files changed

+73
-6
lines changed

4 files changed

+73
-6
lines changed

src/interfaces/chain.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,16 @@ class ChainImpl : public Chain
260260
WAIT_LOCK(cs_main, lock);
261261
return FillBlock(LookupBlockIndex(hash), block, lock);
262262
}
263+
bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override
264+
{
265+
WAIT_LOCK(cs_main, lock);
266+
if (const CBlockIndex* block = LookupBlockIndex(block_hash)) {
267+
if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) {
268+
return FillBlock(ancestor, ancestor_out, lock);
269+
}
270+
}
271+
return FillBlock(nullptr, ancestor_out, lock);
272+
}
263273
bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override
264274
{
265275
WAIT_LOCK(cs_main, lock);
@@ -268,6 +278,14 @@ class ChainImpl : public Chain
268278
if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr;
269279
return FillBlock(ancestor, ancestor_out, lock);
270280
}
281+
bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override
282+
{
283+
WAIT_LOCK(cs_main, lock);
284+
const CBlockIndex* block1 = LookupBlockIndex(block_hash1);
285+
const CBlockIndex* block2 = LookupBlockIndex(block_hash2);
286+
const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr;
287+
return FillBlock(ancestor, ancestor_out, lock) & FillBlock(block1, block1_out, lock) & FillBlock(block2, block2_out, lock);
288+
}
271289
void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); }
272290
double guessVerificationProgress(const uint256& block_hash) override
273291
{

src/interfaces/chain.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,12 +146,24 @@ class Chain
146146
//! or contents.
147147
virtual bool findBlock(const uint256& hash, const FoundBlock& block={}) = 0;
148148

149+
//! Find ancestor of block at specified height and optionally return
150+
//! ancestor information.
151+
virtual bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out={}) = 0;
152+
149153
//! Return whether block descends from a specified ancestor, and
150154
//! optionally return ancestor information.
151155
virtual bool findAncestorByHash(const uint256& block_hash,
152156
const uint256& ancestor_hash,
153157
const FoundBlock& ancestor_out={}) = 0;
154158

159+
//! Find most recent common ancestor between two blocks and optionally
160+
//! return block information.
161+
virtual bool findCommonAncestor(const uint256& block_hash1,
162+
const uint256& block_hash2,
163+
const FoundBlock& ancestor_out={},
164+
const FoundBlock& block1_out={},
165+
const FoundBlock& block2_out={}) = 0;
166+
155167
//! Look up unspent output information. Returns coins in the mempool and in
156168
//! the current chain UTXO set. Iterates through all the keys in the map and
157169
//! populates the values.

src/test/interfaces_tests.cpp

Lines changed: 37 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

@@ -44,6 +47,16 @@ BOOST_AUTO_TEST_CASE(findBlock)
4447
BOOST_CHECK(!chain->findBlock({}, FoundBlock()));
4548
}
4649

50+
BOOST_AUTO_TEST_CASE(findAncestorByHeight)
51+
{
52+
auto chain = interfaces::MakeChain(m_node);
53+
auto& active = ChainActive();
54+
uint256 hash;
55+
BOOST_CHECK(chain->findAncestorByHeight(active[20]->GetBlockHash(), 10, FoundBlock().hash(hash)));
56+
BOOST_CHECK_EQUAL(hash, active[10]->GetBlockHash());
57+
BOOST_CHECK(!chain->findAncestorByHeight(active[10]->GetBlockHash(), 20));
58+
}
59+
4760
BOOST_AUTO_TEST_CASE(findAncestorByHash)
4861
{
4962
auto chain = interfaces::MakeChain(m_node);
@@ -54,4 +67,28 @@ BOOST_AUTO_TEST_CASE(findAncestorByHash)
5467
BOOST_CHECK(!chain->findAncestorByHash(active[10]->GetBlockHash(), active[20]->GetBlockHash()));
5568
}
5669

70+
BOOST_AUTO_TEST_CASE(findCommonAncestor)
71+
{
72+
auto chain = interfaces::MakeChain(m_node);
73+
auto& active = ChainActive();
74+
auto* orig_tip = active.Tip();
75+
for (int i = 0; i < 10; ++i) {
76+
BlockValidationState state;
77+
ChainstateActive().InvalidateBlock(state, Params(), active.Tip());
78+
}
79+
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight - 10);
80+
coinbaseKey.MakeNewKey(true);
81+
for (int i = 0; i < 20; ++i) {
82+
CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey()));
83+
}
84+
BOOST_CHECK_EQUAL(active.Height(), orig_tip->nHeight + 10);
85+
uint256 fork_hash;
86+
int fork_height;
87+
int orig_height;
88+
BOOST_CHECK(chain->findCommonAncestor(orig_tip->GetBlockHash(), active.Tip()->GetBlockHash(), FoundBlock().height(fork_height).hash(fork_hash), FoundBlock().height(orig_height)));
89+
BOOST_CHECK_EQUAL(orig_height, orig_tip->nHeight);
90+
BOOST_CHECK_EQUAL(fork_height, orig_tip->nHeight - 10);
91+
BOOST_CHECK_EQUAL(fork_hash, active[fork_height]->GetBlockHash());
92+
}
93+
5794
BOOST_AUTO_TEST_SUITE_END()

src/wallet/rpcwallet.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,8 +1581,9 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
15811581
uint256 blockId;
15821582
if (!request.params[0].isNull() && !request.params[0].get_str().empty()) {
15831583
blockId = ParseHashV(request.params[0], "blockhash");
1584-
height = locked_chain->findFork(blockId, &altheight);
1585-
if (!height) {
1584+
height.emplace();
1585+
altheight.emplace();
1586+
if (!pwallet->chain().findCommonAncestor(blockId, pwallet->GetLastBlockHash(), /* ancestor out */ FoundBlock().height(*height), /* blockId out */ FoundBlock().height(*altheight))) {
15861587
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
15871588
}
15881589
}
@@ -1601,8 +1602,7 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16011602

16021603
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
16031604

1604-
const Optional<int> tip_height = locked_chain->getHeight();
1605-
int depth = tip_height && height ? (1 + *tip_height - *height) : -1;
1605+
int depth = height ? pwallet->GetLastBlockHeight() + 1 - *height : -1;
16061606

16071607
UniValue transactions(UniValue::VARR);
16081608

@@ -1634,8 +1634,8 @@ static UniValue listsinceblock(const JSONRPCRequest& request)
16341634
--*altheight;
16351635
}
16361636

1637-
int last_height = tip_height ? *tip_height + 1 - target_confirms : -1;
1638-
uint256 lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : uint256();
1637+
uint256 lastblock;
1638+
CHECK_NONFATAL(pwallet->chain().findAncestorByHeight(pwallet->GetLastBlockHash(), pwallet->GetLastBlockHeight() + 1 - target_confirms, FoundBlock().hash(lastblock)));
16391639

16401640
UniValue ret(UniValue::VOBJ);
16411641
ret.pushKV("transactions", transactions);

0 commit comments

Comments
 (0)