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

Rework validation logic for assumeutxo #27746

Merged
Merged
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
5 changes: 2 additions & 3 deletions doc/design/assumeutxo.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@ respectively generate and load UTXO snapshots. The utility script

- A new block index `nStatus` flag is introduced, `BLOCK_ASSUMED_VALID`, to mark block
index entries that are required to be assumed-valid by a chainstate created
from a UTXO snapshot. This flag is mostly used as a way to modify certain
from a UTXO snapshot. This flag is used as a way to modify certain
CheckBlockIndex() logic to account for index entries that are pending validation by a
chainstate running asynchronously in the background. We also use this flag to control
which index entries are added to setBlockIndexCandidates during LoadBlockIndex().
chainstate running asynchronously in the background.

- The concept of UTXO snapshots is treated as an implementation detail that lives
behind the ChainstateManager interface. The external presentation of the changes
Expand Down
3 changes: 1 addition & 2 deletions src/bench/load_external.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,13 @@ static void LoadExternalBlockFile(benchmark::Bench& bench)
fclose(file);
}

Chainstate& chainstate{testing_setup->m_node.chainman->ActiveChainstate()};
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
FlatFilePos pos;
bench.run([&] {
// "rb" is "binary, O_RDONLY", positioned to the start of the file.
// The file will be closed by LoadExternalBlockFile().
FILE* file{fsbridge::fopen(blkfile, "rb")};
chainstate.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
testing_setup->m_node.chainman->LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
});
fs::remove(blkfile);
}
Expand Down
20 changes: 14 additions & 6 deletions src/chain.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,10 @@ enum BlockStatus : uint32_t {
BLOCK_VALID_TRANSACTIONS = 3,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0ce805b: why isn't BLOCK_VALID_TRANSACTIONS also alternatively ASSUMED_VALID?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

re: #27746 (comment)

0ce805b: why isn't BLOCK_VALID_TRANSACTIONS also alternatively ASSUMED_VALID?

I'm not sure what this review comment is asking. The code comment above still seems accurate to me, but it maybe it could be improved.

Copy link
Member

@Sjors Sjors Aug 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's related to #27746 (comment)

(it suggests a further refactor, not changing the comment)


//! Outputs do not overspend inputs, no double spends, coinbase output ok, no immature coinbase spends, BIP30.
//! Implies all parents are also at least CHAIN.
//! Implies all parents are either at least VALID_CHAIN, or are ASSUMED_VALID
BLOCK_VALID_CHAIN = 4,

//! Scripts & signatures ok. Implies all parents are also at least SCRIPTS.
//! Scripts & signatures ok. Implies all parents are either at least VALID_SCRIPTS, or are ASSUMED_VALID.
BLOCK_VALID_SCRIPTS = 5,

//! All validity bits.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0ce805b: maybe make this:

//! All (non-assumed) validity bits.

Which makes functions that use this more readable:

Scherm­afbeelding 2023-07-28 om 15 44 16

Expand All @@ -134,10 +134,18 @@ enum BlockStatus : uint32_t {
BLOCK_OPT_WITNESS = 128, //!< block data in blk*.dat was received with a witness-enforcing client

/**
* If set, this indicates that the block index entry is assumed-valid.
* Certain diagnostics will be skipped in e.g. CheckBlockIndex().
* It almost certainly means that the block's full validation is pending
* on a background chainstate. See `doc/design/assumeutxo.md`.
* If ASSUMED_VALID is set, it means that this block has not been validated
* and has validity status less than VALID_SCRIPTS. Also that it may have
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0ce805b: why not less than BLOCK_VALID_TRANSACTIONS? Since the minimum requirement to download an assumed-valid block is that we have its headers, i.e. we're at BLOCK_VALID_TREE or above.

Same question for RaiseValidity, which this PR doesn't touch. Contrast that to CheckBlockIndex() which skips checks for BLOCK_VALID_TRANSACTIONS, BLOCK_VALID_CHAIN and BLOCK_VALID_SCRIPTS when assume valid is set.

I tried changing BLOCK_VALID_SCRIPTS to BLOCK_VALID_TRANSACTIONS in PopulateAndValidateSnapshot:

        // Mark unvalidated block index entries beneath the snapshot base block as assumed-valid.
        if (!index->IsValid(BLOCK_VALID_SCRIPTS)) {

That didn't break a test and makes more intuitive sense to me. It also seems more consistent with setBlockIndexCandidates.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not less than BLOCK_VALID_TRANSACTIONS?

I asked myself the same question recently and came to the conclusion that it's because when we finally end up downloading the assumed-valid blocks, there will be a period in time where we've saved the block (and therefore raised its validity to BLOCK_VALID_TRANSACTIONS) but haven't connected it yet to the background chainstate (so it's still ASSUMED_VALID).

* descendant blocks with VALID_SCRIPTS set, because they can be validated
* based on an assumeutxo snapshot.
*
* When an assumeutxo snapshot is loaded, the ASSUMED_VALID flag is added to
* unvalidated blocks at the snapshot height and below. Then, as the background
* validation progresses, and these blocks are validated, the ASSUMED_VALID
* flags are removed. See `doc/design/assumeutxo.md` for details.
*
* This flag is only used to implement checks in CheckBlockIndex() and
* should not be used elsewhere.
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
*/
BLOCK_ASSUMED_VALID = 256,
};
Expand Down
16 changes: 9 additions & 7 deletions src/node/blockstorage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ fs::path BlockManager::GetBlockPosFilename(const FlatFilePos& pos) const
return BlockFileSeq().FileName(pos);
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
}

bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown)
bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown)
{
LOCK(cs_LastBlockFile);

Expand All @@ -644,7 +644,7 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
// when the undo file is keeping up with the block file, we want to flush it explicitly
// when it is lagging behind (more blocks arrive than are being connected), we let the
// undo block write case handle it
finalize_undo = (m_blockfile_info[nFile].nHeightLast == (unsigned int)active_chain.Tip()->nHeight);
finalize_undo = (m_blockfile_info[nFile].nHeightLast == m_undo_height_in_last_blockfile);
nFile++;
if (m_blockfile_info.size() <= nFile) {
m_blockfile_info.resize(nFile + 1);
Expand All @@ -660,6 +660,7 @@ bool BlockManager::FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigne
}
FlushBlockFile(!fKnown, finalize_undo);
m_last_blockfile = nFile;
m_undo_height_in_last_blockfile = 0; // No undo data yet in the new file, so reset our undo-height tracking.
}

m_blockfile_info[nFile].AddBlock(nHeight, nTime);
Expand Down Expand Up @@ -749,8 +750,9 @@ bool BlockManager::WriteUndoDataForBlock(const CBlockUndo& blockundo, BlockValid
// the FindBlockPos function
if (_pos.nFile < m_last_blockfile && static_cast<uint32_t>(block.nHeight) == m_blockfile_info[_pos.nFile].nHeightLast) {
FlushUndoFile(_pos.nFile, true);
} else if (_pos.nFile == m_last_blockfile && static_cast<uint32_t>(block.nHeight) > m_undo_height_in_last_blockfile) {
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
m_undo_height_in_last_blockfile = block.nHeight;
}

// update nUndoPos in block index
block.nUndoPos = _pos.nPos;
block.nStatus |= BLOCK_HAVE_UNDO;
Expand Down Expand Up @@ -839,7 +841,7 @@ bool BlockManager::ReadRawBlockFromDisk(std::vector<uint8_t>& block, const FlatF
return true;
}

FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const FlatFilePos* dbp)
FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, const FlatFilePos* dbp)
{
unsigned int nBlockSize = ::GetSerializeSize(block, CLIENT_VERSION);
FlatFilePos blockPos;
Expand All @@ -852,7 +854,7 @@ FlatFilePos BlockManager::SaveBlockToDisk(const CBlock& block, int nHeight, CCha
// we add BLOCK_SERIALIZATION_HEADER_SIZE only for new blocks since they will have the serialization header added when written to disk.
nBlockSize += static_cast<unsigned int>(BLOCK_SERIALIZATION_HEADER_SIZE);
}
if (!FindBlockPos(blockPos, nBlockSize, nHeight, active_chain, block.GetBlockTime(), position_known)) {
if (!FindBlockPos(blockPos, nBlockSize, nHeight, block.GetBlockTime(), position_known)) {
error("%s: FindBlockPos failed", __func__);
return FlatFilePos();
}
Expand Down Expand Up @@ -905,7 +907,7 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
break; // This error is logged in OpenBlockFile
}
LogPrintf("Reindexing block file blk%05u.dat...\n", (unsigned int)nFile);
chainman.ActiveChainstate().LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
chainman.LoadExternalBlockFile(file, &pos, &blocks_with_unknown_parent);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
Expand All @@ -924,7 +926,7 @@ void ImportBlocks(ChainstateManager& chainman, std::vector<fs::path> vImportFile
FILE* file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", fs::PathToString(path));
chainman.ActiveChainstate().LoadExternalBlockFile(file);
chainman.LoadExternalBlockFile(file);
if (chainman.m_interrupt) {
LogPrintf("Interrupt requested. Exit %s\n", __func__);
return;
Expand Down
18 changes: 15 additions & 3 deletions src/node/blockstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ class BlockValidationState;
class CBlock;
class CBlockFileInfo;
class CBlockUndo;
class CChain;
class CChainParams;
class Chainstate;
class ChainstateManager;
Expand Down Expand Up @@ -94,7 +93,7 @@ class BlockManager
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
void FlushBlockFile(bool fFinalize = false, bool finalize_undo = false);
void FlushUndoFile(int block_file, bool finalize = false);
bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, CChain& active_chain, uint64_t nTime, bool fKnown);
bool FindBlockPos(FlatFilePos& pos, unsigned int nAddSize, unsigned int nHeight, uint64_t nTime, bool fKnown);
bool FindUndoPos(BlockValidationState& state, int nFile, FlatFilePos& pos, unsigned int nAddSize);

FlatFileSeq BlockFileSeq() const;
Expand Down Expand Up @@ -128,6 +127,19 @@ class BlockManager
RecursiveMutex cs_LastBlockFile;
std::vector<CBlockFileInfo> m_blockfile_info;
int m_last_blockfile = 0;

// Track the height of the highest block in m_last_blockfile whose undo
// data has been written. Block data is written to block files in download
// order, but is written to undo files in validation order, which is
// usually in order by height. To avoid wasting disk space, undo files will
// be trimmed whenever the corresponding block file is finalized and
// the height of the highest block written to the block file equals the
// height of the highest block written to the undo file. This is a
// heuristic and can sometimes preemptively trim undo files that will write
// more data later, and sometimes fail to trim undo files that can't have
// more data written later.
unsigned int m_undo_height_in_last_blockfile = 0;

/** Global flag to indicate we should check to see if there are
* block/undo files that should be deleted. Set on startup
* or if we allocate more file space when we're in prune mode
Expand Down Expand Up @@ -202,7 +214,7 @@ class BlockManager
EXCLUSIVE_LOCKS_REQUIRED(::cs_main);

/** Store block on disk. If dbp is not nullptr, then it provides the known position of the block within a block file on disk. */
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, CChain& active_chain, const FlatFilePos* dbp);
FlatFilePos SaveBlockToDisk(const CBlock& block, int nHeight, const FlatFilePos* dbp);

/** Whether running in -prune mode. */
[[nodiscard]] bool IsPruneMode() const { return m_prune_mode; }
Expand Down
2 changes: 1 addition & 1 deletion src/node/chainstate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ ChainstateLoadResult LoadChainstate(ChainstateManager& chainman, const CacheSize

// A reload of the block index is required to recompute setBlockIndexCandidates
// for the fully validated chainstate.
chainman.ActiveChainstate().UnloadBlockIndex();
chainman.ActiveChainstate().ClearBlockIndexCandidates();
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
Comment on lines 222 to +224
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In commit "Move block-arrival information / preciousblock counters to ChainstateManager" (471da5f)

The "reload of the block index" comment now seems out of date. And actually I don't see why this ClearBlockIndexCandidates call is necessary here. After the InitializeChainstate call above, the active chainstate should be newly created and setBlockIndexCandidates should already be empty. So I think it would be good to drop this ClearBlockIndexCandidates call, or add a new comment to explain what it's doing.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this seems redundant; the original call to UnloadBlockIndex() was added in #25740, and if I'm reading correctly I don't think it should have been necessary then either. @jamesob Wondering if you agree, or if we're overlooking something?


auto [init_status, init_error] = CompleteChainstateInitialization(chainman, cache_sizes, options);
if (init_status != ChainstateLoadStatus::SUCCESS) {
Expand Down
7 changes: 3 additions & 4 deletions src/test/blockmanager_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,21 @@ BOOST_AUTO_TEST_CASE(blockmanager_find_block_pos)
.notifications = notifications,
};
BlockManager blockman{m_node.kernel->interrupt, blockman_opts};
CChain chain {};
// simulate adding a genesis block normally
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, nullptr).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
sdaftuar marked this conversation as resolved.
Show resolved Hide resolved
// simulate what happens during reindex
// simulate a well-formed genesis block being found at offset 8 in the blk00000.dat file
// the block is found at offset 8 because there is an 8 byte serialization header
// consisting of 4 magic bytes + 4 length bytes before each block in a well-formed blk file.
FlatFilePos pos{0, BLOCK_SERIALIZATION_HEADER_SIZE};
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, chain, &pos).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
BOOST_CHECK_EQUAL(blockman.SaveBlockToDisk(params->GenesisBlock(), 0, &pos).nPos, BLOCK_SERIALIZATION_HEADER_SIZE);
// now simulate what happens after reindex for the first new block processed
// the actual block contents don't matter, just that it's a block.
// verify that the write position is at offset 0x12d.
// this is a check to make sure that https://github.com/bitcoin/bitcoin/issues/21379 does not recur
// 8 bytes (for serialization header) + 285 (for serialized genesis block) = 293
// add another 8 bytes for the second block's serialization header and we get 293 + 8 = 301
FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1, chain, nullptr)};
FlatFilePos actual{blockman.SaveBlockToDisk(params->GenesisBlock(), 1, nullptr)};
BOOST_CHECK_EQUAL(actual.nPos, BLOCK_SERIALIZATION_HEADER_SIZE + ::GetSerializeSize(params->GenesisBlock(), CLIENT_VERSION) + BLOCK_SERIALIZATION_HEADER_SIZE);
}

Expand Down
2 changes: 1 addition & 1 deletion src/test/coinstatsindex_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ BOOST_FIXTURE_TEST_CASE(coinstatsindex_unclean_shutdown, TestChain100Setup)
LOCK(cs_main);
BlockValidationState state;
BOOST_CHECK(CheckBlock(block, state, params.GetConsensus()));
BOOST_CHECK(chainstate.AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
BOOST_CHECK(m_node.chainman->AcceptBlock(new_block, state, &new_block_index, true, nullptr, nullptr, true));
CCoinsViewCache view(&chainstate.CoinsTip());
BOOST_CHECK(chainstate.ConnectBlock(block, state, new_block_index, view));
}
Expand Down
4 changes: 2 additions & 2 deletions src/test/fuzz/load_external_block_file.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ FUZZ_TARGET_INIT(load_external_block_file, initialize_load_external_block_file)
// Corresponds to the -reindex case (track orphan blocks across files).
FlatFilePos flat_file_pos;
std::multimap<uint256, FlatFilePos> blocks_with_unknown_parent;
g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent);
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file, &flat_file_pos, &blocks_with_unknown_parent);
} else {
// Corresponds to the -loadblock= case (orphan blocks aren't tracked across files).
g_setup->m_node.chainman->ActiveChainstate().LoadExternalBlockFile(fuzzed_block_file);
g_setup->m_node.chainman->LoadExternalBlockFile(fuzzed_block_file);
}
}
17 changes: 17 additions & 0 deletions src/test/util/chainstate.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ CreateAndActivateUTXOSnapshot(
// This is a stripped-down version of node::LoadChainstate which
// preserves the block index.
LOCK(::cs_main);
CBlockIndex *orig_tip = node.chainman->ActiveChainstate().m_chain.Tip();
uint256 gen_hash = node.chainman->ActiveChainstate().m_chain[0]->GetBlockHash();
node.chainman->ResetChainstates();
node.chainman->InitializeChainstate(node.mempool.get());
Expand All @@ -83,6 +84,22 @@ CreateAndActivateUTXOSnapshot(
chain.setBlockIndexCandidates.insert(node.chainman->m_blockman.LookupBlockIndex(gen_hash));
chain.LoadChainTip();
node.chainman->MaybeRebalanceCaches();

// Reset the HAVE_DATA flags below the snapshot height, simulating
sdaftuar marked this conversation as resolved.
Show resolved Hide resolved
// never-having-downloaded them in the first place.
// TODO: perhaps we could improve this by using pruning to delete
// these blocks instead
CBlockIndex *pindex = orig_tip;
while (pindex && pindex != chain.m_chain.Tip()) {
pindex->nStatus &= ~BLOCK_HAVE_DATA;
pindex->nStatus &= ~BLOCK_HAVE_UNDO;
// We have to set the ASSUMED_VALID flag, because otherwise it
// would not be possible to have a block index entry without HAVE_DATA
// and with nTx > 0 (since we aren't setting the pruned flag);
// see CheckBlockIndex().
pindex->nStatus |= BLOCK_ASSUMED_VALID;
sdaftuar marked this conversation as resolved.
Show resolved Hide resolved
pindex = pindex->pprev;
}
}
BlockValidationState state;
if (!node.chainman->ActiveChainstate().ActivateBestChain(state)) {
Expand Down
24 changes: 14 additions & 10 deletions src/test/validation_chainstate_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,13 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
// After adding some blocks to the tip, best block should have changed.
BOOST_CHECK(::g_best_block != curr_tip);

// Grab block 1 from disk; we'll add it to the background chain later.
std::shared_ptr<CBlock> pblockone = std::make_shared<CBlock>();
{
LOCK(::cs_main);
chainman.m_blockman.ReadBlockFromDisk(*pblockone, *chainman.ActiveChain()[1]);
}

BOOST_REQUIRE(CreateAndActivateUTXOSnapshot(
this, NoMalleation, /*reset_chainstate=*/ true));

Expand Down Expand Up @@ -104,11 +111,7 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
assert(false);
}()};

// Create a block to append to the validation chain.
std::vector<CMutableTransaction> noTxns;
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey()) << OP_CHECKSIG;
CBlock validation_block = this->CreateBlock(noTxns, scriptPubKey, background_cs);
auto pblock = std::make_shared<const CBlock>(validation_block);
// Append the first block to the background chain.
BlockValidationState state;
CBlockIndex* pindex = nullptr;
const CChainParams& chainparams = Params();
Expand All @@ -118,17 +121,18 @@ BOOST_FIXTURE_TEST_CASE(chainstate_update_tip, TestChain100Setup)
// once it is changed to support multiple chainstates.
{
LOCK(::cs_main);
bool checked = CheckBlock(*pblock, state, chainparams.GetConsensus());
bool checked = CheckBlock(*pblockone, state, chainparams.GetConsensus());
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
BOOST_CHECK(checked);
bool accepted = background_cs.AcceptBlock(
pblock, state, &pindex, true, nullptr, &newblock, true);
bool accepted = chainman.AcceptBlock(
pblockone, state, &pindex, true, nullptr, &newblock, true);
BOOST_CHECK(accepted);
}

// UpdateTip is called here
bool block_added = background_cs.ActivateBestChain(state, pblock);
bool block_added = background_cs.ActivateBestChain(state, pblockone);

// Ensure tip is as expected
BOOST_CHECK_EQUAL(background_cs.m_chain.Tip()->GetBlockHash(), validation_block.GetHash());
BOOST_CHECK_EQUAL(background_cs.m_chain.Tip()->GetBlockHash(), pblockone->GetHash());

// g_best_block should be unchanged after adding a block to the background
// validation chain.
Expand Down
Loading