Skip to content

Commit b7e3600

Browse files
committed
Merge bitcoin/bitcoin#21526: validation: UpdateTip/CheckBlockIndex assumeutxo support
673a5bd test: validation: add unittest for UpdateTip behavior (James O'Beirne) 2705570 test: refactor: separate CreateBlock in TestChain100Setup (James O'Beirne) 298bf5d test: refactor: declare NoMalleation const auto (James O'Beirne) 0712009 move-only: unittest: add test/util/chainstate.h (James O'Beirne) 8f5710f validation: fix CheckBlockIndex for multiple chainstates (James O'Beirne) 5a80773 validation: insert assumed-valid block index entries into candidates (James O'Beirne) 01a9b8f validation: set BLOCK_ASSUMED_VALID during snapshot load (James O'Beirne) 42b2520 chain: add BLOCK_ASSUMED_VALID for use with assumeutxo (James O'Beirne) b217020 validation: change UpdateTip for multiple chainstates (James O'Beirne) 665072a doc: add comment for g_best_block (James O'Beirne) ac4051d refactor: remove unused assumeutxo methods (James O'Beirne) 9f6bb53 validation: add chainman ref to CChainState (James O'Beirne) Pull request description: This is part of the [assumeutxo project](https://github.com/bitcoin/bitcoin/projects/11) (parent PR: #15606) --- Modify UpdateTip and CheckBlockIndex for use with multiple chainstates. Includes a new unittest verifying `g_best_block` behavior (previously untested at the unit level) and various changes necessary for running and testing `ProcessNewBlock()`-like behavior on the background validation chainstate. This changeset introduces a new block index `nStatus` flag called `BLOCK_ASSUMED_VALID`, and it is applied to block index entries that are beneath the UTXO snapshot base block upon snapshot load. Once each block is validated (during async background validation), the flag is removed. This allows us to avoid (ab)using `BLOCK_VALID_*` flags for snapshot chain block entries, and preserves the original meaning of those flags. Note: this PR previously incorporated changes to `LoadBlockIndex()` and `RewindBlockIndex()` as noted in Russ' comments below, but once I generated the changes necessary to test the UpdateTip change, I decided to split this changes out into another PR due to the size of this one. ACKs for top commit: achow101: ACK 673a5bd jonatack: Code-review re-ACK 673a5bd reviewed diff, rebased to master/debug build/ran unit+functional tests naumenkogs: ACK 673a5bd fjahr: Code review ACK 673a5bd ariard: utACK 673a5bd ryanofsky: Code review ACK 673a5bd. Just linker fix and split commit changes mentioned bitcoin/bitcoin#21526 (comment) since last review benthecarman: ACK 673a5bd Tree-SHA512: 0a6dc23d041b27ed9fd0ee1f3e5971b92fb1d2df2fc9b655d5dc48594235321ab1798d06de2ec55482ac3966a9ed56de8d56e9e29cae75bbe8690bafc2dda383
2 parents d8b4b30 + 673a5bd commit b7e3600

10 files changed

+314
-119
lines changed

src/Makefile.test_util.include

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ EXTRA_LIBRARIES += \
99

1010
TEST_UTIL_H = \
1111
test/util/blockfilter.h \
12+
test/util/chainstate.h \
1213
test/util/logging.h \
1314
test/util/mining.h \
1415
test/util/net.h \

src/chain.h

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,15 @@ enum BlockStatus: uint32_t {
126126
BLOCK_FAILED_CHILD = 64, //!< descends from failed block
127127
BLOCK_FAILED_MASK = BLOCK_FAILED_VALID | BLOCK_FAILED_CHILD,
128128

129-
BLOCK_OPT_WITNESS = 128, //!< block data in blk*.data was received with a witness-enforcing client
129+
BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client
130+
131+
/**
132+
* If set, this indicates that the block index entry is assumed-valid.
133+
* Certain diagnostics will be skipped in e.g. CheckBlockIndex().
134+
* It almost certainly means that the block's full validation is pending
135+
* on a background chainstate. See `doc/assumeutxo.md`.
136+
*/
137+
BLOCK_ASSUMED_VALID = 256,
130138
};
131139

132140
/** The block chain is a tree shaped structure starting with the
@@ -300,14 +308,24 @@ class CBlockIndex
300308
return ((nStatus & BLOCK_VALID_MASK) >= nUpTo);
301309
}
302310

311+
//! @returns true if the block is assumed-valid; this means it is queued to be
312+
//! validated by a background chainstate.
313+
bool IsAssumedValid() const { return nStatus & BLOCK_ASSUMED_VALID; }
314+
303315
//! Raise the validity level of this block index entry.
304316
//! Returns true if the validity was changed.
305317
bool RaiseValidity(enum BlockStatus nUpTo)
306318
{
307319
assert(!(nUpTo & ~BLOCK_VALID_MASK)); // Only validity flags allowed.
308-
if (nStatus & BLOCK_FAILED_MASK)
309-
return false;
320+
if (nStatus & BLOCK_FAILED_MASK) return false;
321+
310322
if ((nStatus & BLOCK_VALID_MASK) < nUpTo) {
323+
// If this block had been marked assumed-valid and we're raising
324+
// its validity to a certain point, there is no longer an assumption.
325+
if (nStatus & BLOCK_ASSUMED_VALID && nUpTo >= BLOCK_VALID_SCRIPTS) {
326+
nStatus &= ~BLOCK_ASSUMED_VALID;
327+
}
328+
311329
nStatus = (nStatus & ~BLOCK_VALID_MASK) | nUpTo;
312330
return true;
313331
}

src/test/util/chainstate.h

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) 2021 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
//
5+
#ifndef BITCOIN_TEST_UTIL_CHAINSTATE_H
6+
#define BITCOIN_TEST_UTIL_CHAINSTATE_H
7+
8+
#include <clientversion.h>
9+
#include <fs.h>
10+
#include <node/context.h>
11+
#include <node/utxo_snapshot.h>
12+
#include <rpc/blockchain.h>
13+
#include <validation.h>
14+
15+
#include <univalue.h>
16+
17+
#include <boost/test/unit_test.hpp>
18+
19+
const auto NoMalleation = [](CAutoFile& file, SnapshotMetadata& meta){};
20+
21+
/**
22+
* Create and activate a UTXO snapshot, optionally providing a function to
23+
* malleate the snapshot.
24+
*/
25+
template<typename F = decltype(NoMalleation)>
26+
static bool
27+
CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root, F malleation = NoMalleation)
28+
{
29+
// Write out a snapshot to the test's tempdir.
30+
//
31+
int height;
32+
WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
33+
fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height);
34+
FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
35+
CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};
36+
37+
UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile);
38+
BOOST_TEST_MESSAGE(
39+
"Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());
40+
41+
// Read the written snapshot in and then activate it.
42+
//
43+
FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
44+
CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION};
45+
SnapshotMetadata metadata;
46+
auto_infile >> metadata;
47+
48+
malleation(auto_infile, metadata);
49+
50+
return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true);
51+
}
52+
53+
54+
#endif // BITCOIN_TEST_UTIL_CHAINSTATE_H

src/test/util/setup_common.cpp

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,11 +234,14 @@ void TestChain100Setup::mineBlocks(int num_blocks)
234234
}
235235
}
236236

237-
CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns, const CScript& scriptPubKey)
237+
CBlock TestChain100Setup::CreateBlock(
238+
const std::vector<CMutableTransaction>& txns,
239+
const CScript& scriptPubKey,
240+
CChainState& chainstate)
238241
{
239242
const CChainParams& chainparams = Params();
240243
CTxMemPool empty_pool;
241-
CBlock block = BlockAssembler(m_node.chainman->ActiveChainstate(), empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block;
244+
CBlock block = BlockAssembler(chainstate, empty_pool, chainparams).CreateNewBlock(scriptPubKey)->block;
242245

243246
Assert(block.vtx.size() == 1);
244247
for (const CMutableTransaction& tx : txns) {
@@ -248,6 +251,20 @@ CBlock TestChain100Setup::CreateAndProcessBlock(const std::vector<CMutableTransa
248251

249252
while (!CheckProofOfWork(block.GetHash(), block.nBits, chainparams.GetConsensus())) ++block.nNonce;
250253

254+
return block;
255+
}
256+
257+
CBlock TestChain100Setup::CreateAndProcessBlock(
258+
const std::vector<CMutableTransaction>& txns,
259+
const CScript& scriptPubKey,
260+
CChainState* chainstate)
261+
{
262+
if (!chainstate) {
263+
chainstate = &Assert(m_node.chainman)->ActiveChainstate();
264+
}
265+
266+
const CChainParams& chainparams = Params();
267+
const CBlock block = this->CreateBlock(txns, scriptPubKey, *chainstate);
251268
std::shared_ptr<const CBlock> shared_pblock = std::make_shared<const CBlock>(block);
252269
Assert(m_node.chainman)->ProcessNewBlock(chainparams, shared_pblock, true, nullptr);
253270

src/test/util/setup_common.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,20 @@ struct TestChain100Setup : public RegTestingSetup {
119119
/**
120120
* Create a new block with just given transactions, coinbase paying to
121121
* scriptPubKey, and try to add it to the current chain.
122+
* If no chainstate is specified, default to the active.
122123
*/
123124
CBlock CreateAndProcessBlock(const std::vector<CMutableTransaction>& txns,
124-
const CScript& scriptPubKey);
125+
const CScript& scriptPubKey,
126+
CChainState* chainstate = nullptr);
127+
128+
/**
129+
* Create a new block with just given transactions, coinbase paying to
130+
* scriptPubKey.
131+
*/
132+
CBlock CreateBlock(
133+
const std::vector<CMutableTransaction>& txns,
134+
const CScript& scriptPubKey,
135+
CChainState& chainstate);
125136

126137
//! Mine a series of new blocks on the active chain.
127138
void mineBlocks(int num_blocks);

src/test/validation_chainstate_tests.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
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>
56
#include <random.h>
67
#include <uint256.h>
78
#include <consensus/validation.h>
89
#include <sync.h>
10+
#include <rpc/blockchain.h>
11+
#include <test/util/chainstate.h>
912
#include <test/util/setup_common.h>
1013
#include <validation.h>
1114

@@ -73,4 +76,77 @@ BOOST_AUTO_TEST_CASE(validation_chainstate_resize_caches)
7376
WITH_LOCK(::cs_main, manager.Unload());
7477
}
7578

79+
//! Test UpdateTip behavior for both active and background chainstates.
80+
//!
81+
//! When run on the background chainstate, UpdateTip should do a subset
82+
//! of what it does for the active chainstate.
83+
BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
84+
{
85+
ChainstateManager& chainman = *Assert(m_node.chainman);
86+
uint256 curr_tip = ::g_best_block;
87+
88+
// Mine 10 more blocks, putting at us height 110 where a valid assumeutxo value can
89+
// be found.
90+
mineBlocks(10);
91+
92+
// After adding some blocks to the tip, best block should have changed.
93+
BOOST_CHECK(::g_best_block != curr_tip);
94+
95+
BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(m_node, m_path_root));
96+
97+
// Ensure our active chain is the snapshot chainstate.
98+
BOOST_CHECK(chainman.IsSnapshotActive());
99+
100+
curr_tip = ::g_best_block;
101+
102+
// Mine a new block on top of the activated snapshot chainstate.
103+
mineBlocks(1); // Defined in TestChain100Setup.
104+
105+
// After adding some blocks to the snapshot tip, best block should have changed.
106+
BOOST_CHECK(::g_best_block != curr_tip);
107+
108+
curr_tip = ::g_best_block;
109+
110+
CChainState* background_cs;
111+
112+
BOOST_CHECK_EQUAL(chainman.GetAll().size(), 2);
113+
for (CChainState* cs : chainman.GetAll()) {
114+
if (cs != &chainman.ActiveChainstate()) {
115+
background_cs = cs;
116+
}
117+
}
118+
BOOST_CHECK(background_cs);
119+
120+
// Create a block to append to the validation chain.
121+
std::vector<CMutableTransaction> noTxns;
122+
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
123+
CBlock validation_block = this->CreateBlock(noTxns, scriptPubKey, *background_cs);
124+
auto pblock = std::make_shared<const CBlock>(validation_block);
125+
BlockValidationState state;
126+
CBlockIndex* pindex = nullptr;
127+
const CChainParams& chainparams = Params();
128+
bool newblock = false;
129+
130+
// TODO: much of this is inlined from ProcessNewBlock(); just reuse PNB()
131+
// once it is changed to support multiple chainstates.
132+
{
133+
LOCK(::cs_main);
134+
bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
135+
BOOST_CHECK(checked);
136+
bool accepted = background_cs->AcceptBlock(
137+
pblock, state, &pindex, true, nullptr, &newblock);
138+
BOOST_CHECK(accepted);
139+
}
140+
// UpdateTip is called here
141+
bool block_added = background_cs->ActivateBestChain(state, pblock);
142+
143+
// Ensure tip is as expected
144+
BOOST_CHECK_EQUAL(background_cs->m_chain.Tip()->GetBlockHash(), validation_block.GetHash());
145+
146+
// g_best_block should be unchanged after adding a block to the background
147+
// validation chain.
148+
BOOST_CHECK(block_added);
149+
BOOST_CHECK_EQUAL(curr_tip, ::g_best_block);
150+
}
151+
76152
BOOST_AUTO_TEST_SUITE_END()

src/test/validation_chainstatemanager_tests.cpp

Lines changed: 11 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
#include <random.h>
99
#include <rpc/blockchain.h>
1010
#include <sync.h>
11+
#include <test/util/chainstate.h>
1112
#include <test/util/setup_common.h>
1213
#include <uint256.h>
1314
#include <validation.h>
1415
#include <validationinterface.h>
1516

1617
#include <tinyformat.h>
17-
#include <univalue.h>
1818

1919
#include <vector>
2020

@@ -44,7 +44,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
4444

4545
BOOST_CHECK(!manager.IsSnapshotActive());
4646
BOOST_CHECK(!manager.IsSnapshotValidated());
47-
BOOST_CHECK(!manager.IsBackgroundIBD(&c1));
4847
auto all = manager.GetAll();
4948
BOOST_CHECK_EQUAL_COLLECTIONS(all.begin(), all.end(), chainstates.begin(), chainstates.end());
5049

@@ -57,9 +56,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
5756
auto exp_tip = c1.m_chain.Tip();
5857
BOOST_CHECK_EQUAL(active_tip, exp_tip);
5958

60-
auto& validated_cs = manager.ValidatedChainstate();
61-
BOOST_CHECK_EQUAL(&validated_cs, &c1);
62-
6359
BOOST_CHECK(!manager.SnapshotBlockhash().has_value());
6460

6561
// Create a snapshot-based chainstate.
@@ -81,8 +77,8 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
8177

8278
BOOST_CHECK(manager.IsSnapshotActive());
8379
BOOST_CHECK(!manager.IsSnapshotValidated());
84-
BOOST_CHECK(manager.IsBackgroundIBD(&c1));
85-
BOOST_CHECK(!manager.IsBackgroundIBD(&c2));
80+
BOOST_CHECK_EQUAL(&c2, &manager.ActiveChainstate());
81+
BOOST_CHECK(&c1 != &manager.ActiveChainstate());
8682
auto all2 = manager.GetAll();
8783
BOOST_CHECK_EQUAL_COLLECTIONS(all2.begin(), all2.end(), chainstates.begin(), chainstates.end());
8884

@@ -99,16 +95,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager)
9995
// CCoinsViewCache instances.
10096
BOOST_CHECK(exp_tip != exp_tip2);
10197

102-
auto& validated_cs2 = manager.ValidatedChainstate();
103-
BOOST_CHECK_EQUAL(&validated_cs2, &c1);
104-
105-
auto& validated_chain = manager.ValidatedChain();
106-
BOOST_CHECK_EQUAL(&validated_chain, &c1.m_chain);
107-
108-
auto validated_tip = manager.ValidatedTip();
109-
exp_tip = c1.m_chain.Tip();
110-
BOOST_CHECK_EQUAL(validated_tip, exp_tip);
111-
11298
// Let scheduler events finish running to avoid accessing memory that is going to be unloaded
11399
SyncWithValidationInterfaceQueue();
114100

@@ -168,36 +154,6 @@ BOOST_AUTO_TEST_CASE(chainstatemanager_rebalance_caches)
168154
BOOST_CHECK_CLOSE(c2.m_coinsdb_cache_size_bytes, max_cache * 0.95, 1);
169155
}
170156

171-
auto NoMalleation = [](CAutoFile& file, SnapshotMetadata& meta){};
172-
173-
template<typename F = decltype(NoMalleation)>
174-
static bool
175-
CreateAndActivateUTXOSnapshot(NodeContext& node, const fs::path root, F malleation = NoMalleation)
176-
{
177-
// Write out a snapshot to the test's tempdir.
178-
//
179-
int height;
180-
WITH_LOCK(::cs_main, height = node.chainman->ActiveHeight());
181-
fs::path snapshot_path = root / tfm::format("test_snapshot.%d.dat", height);
182-
FILE* outfile{fsbridge::fopen(snapshot_path, "wb")};
183-
CAutoFile auto_outfile{outfile, SER_DISK, CLIENT_VERSION};
184-
185-
UniValue result = CreateUTXOSnapshot(node, node.chainman->ActiveChainstate(), auto_outfile);
186-
BOOST_TEST_MESSAGE(
187-
"Wrote UTXO snapshot to " << snapshot_path.make_preferred().string() << ": " << result.write());
188-
189-
// Read the written snapshot in and then activate it.
190-
//
191-
FILE* infile{fsbridge::fopen(snapshot_path, "rb")};
192-
CAutoFile auto_infile{infile, SER_DISK, CLIENT_VERSION};
193-
SnapshotMetadata metadata;
194-
auto_infile >> metadata;
195-
196-
malleation(auto_infile, metadata);
197-
198-
return node.chainman->ActivateSnapshot(auto_infile, metadata, /*in_memory*/ true);
199-
}
200-
201157
//! Test basic snapshot activation.
202158
BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
203159
{
@@ -321,27 +277,27 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup)
321277
{
322278
LOCK(::cs_main);
323279
size_t coins_in_active{0};
324-
size_t coins_in_ibd{0};
325-
size_t coins_missing_ibd{0};
280+
size_t coins_in_background{0};
281+
size_t coins_missing_from_background{0};
326282

327283
for (CChainState* chainstate : chainman.GetAll()) {
328284
BOOST_TEST_MESSAGE("Checking coins in " << chainstate->ToString());
329285
CCoinsViewCache& coinscache = chainstate->CoinsTip();
330-
bool is_ibd = chainman.IsBackgroundIBD(chainstate);
286+
bool is_background = chainstate != &chainman.ActiveChainstate();
331287

332288
for (CTransactionRef& txn : m_coinbase_txns) {
333289
COutPoint op{txn->GetHash(), 0};
334290
if (coinscache.HaveCoin(op)) {
335-
(is_ibd ? coins_in_ibd : coins_in_active)++;
336-
} else if (is_ibd) {
337-
coins_missing_ibd++;
291+
(is_background ? coins_in_background : coins_in_active)++;
292+
} else if (is_background) {
293+
coins_missing_from_background++;
338294
}
339295
}
340296
}
341297

342298
BOOST_CHECK_EQUAL(coins_in_active, initial_total_coins + new_coins);
343-
BOOST_CHECK_EQUAL(coins_in_ibd, initial_total_coins);
344-
BOOST_CHECK_EQUAL(coins_missing_ibd, new_coins);
299+
BOOST_CHECK_EQUAL(coins_in_background, initial_total_coins);
300+
BOOST_CHECK_EQUAL(coins_missing_from_background, new_coins);
345301
}
346302

347303
// Snapshot should refuse to load after one has already loaded.

0 commit comments

Comments
 (0)