Skip to content

Commit 5057adf

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#25349: CBlockIndex/CDiskBlockIndex improvements for safety, consistent behavior
3a61fc5 refactor: move CBlockIndex#ToString() from header to implementation (Jon Atack) 57865eb CDiskBlockIndex: rename GetBlockHash() to ConstructBlockHash() (Jon Atack) 99e8ec8 CDiskBlockIndex: remove unused ToString() class member (Jon Atack) 14aeece CBlockIndex: ensure phashBlock is not nullptr before dereferencing (Jon Atack) Pull request description: Fix a few design issues, potential footguns and inconsistent behavior in the CBlockIndex and CDiskBlockIndex classes. - Ensure phashBlock in `CBlockIndex#GetBlockHash()` is not nullptr before dereferencing and remove a now-redundant assert preceding a GetBlockHash() caller. This protects against UB here, and in case of failure (which would indicate a consensus bug), the debug log will print `bitcoind: chain.h:265: uint256 CBlockIndex::GetBlockHash() const: Assertion 'phashBlock != nullptr' failed. Aborted` instead of `Segmentation fault`. - Remove the unused `CDiskBlockIndex#ToString()` class member, and mark the inherited `CBlockIndex#ToString()` public interface member as deleted to disallow calling it in the derived CDiskBlockIndex class. - Rename the `CDiskBlockIndex GetBlockHash()` class member to `ConstructBlockHash()`, which also makes sense as they perform different operations to return a blockhash, and mark the inherited `CBlockIndex#GetBlockHash()` public interface member as deleted to disallow calling it in the derived CDiskBlockIndex class. - Move `CBlockIndex#ToString()` from header to implementation, which also allows dropping `tinyformat.h` from the header file. Rationale and discussion regarding the CDiskBlockIndex changes: Here is a failing test on master that demonstrates the inconsistent behavior of the current design: calling the same inherited public interface functions on the same CDiskBlockIndex object should yield identical behavior, but does not. ```diff diff --git a/src/test/validation_chainstatemanager_tests.cpp b/src/test/validation_chainstatemanager_tests.cpp index 6dc522b..dac3840f32 100644 --- a/src/test/validation_chainstatemanager_tests.cpp +++ b/src/test/validation_chainstatemanager_tests.cpp @@ -240,6 +240,15 @@ BOOST_FIXTURE_TEST_CASE(chainstatemanager_activate_snapshot, TestChain100Setup) const CBlockIndex* tip = chainman.ActiveTip(); BOOST_CHECK_EQUAL(tip->nChainTx, au_data.nChainTx); + // CDiskBlockIndex "is a" CBlockIndex, as it publicly inherits from it. + // Test that calling the same inherited interface functions on the same + // object yields identical behavior. + CDiskBlockIndex index{tip}; + CBlockIndex *pB = &index; + CDiskBlockIndex *pD = &index; + BOOST_CHECK_EQUAL(pB->GetBlockHash(), pD->GetBlockHash()); + BOOST_CHECK_EQUAL(pB->ToString(), pD->ToString()); ``` (build and run: `$ ./src/test/test_bitcoin -t validation_chainstatemanager_tests`) The GetBlockHash() test assertion only passes on master because the different methods invoked by the current design happen to return the same result. If one of the two is changed, it fails like the ToString() assertion does. Redefining inherited non-virtual functions is well-documented as incorrect design to avoid inconsistent behavior (see Scott Meyers, Effective C++, Item 36). Class usage is confusing when the behavior depends on the pointer definition instead of the object definition (static binding happening where dynamic binding was expected). This can lead to unsuspected or hard-to-track bugs. Outside of critical hot spots, correctness usually comes before optimisation, but the current design dates back to main.cpp and it may possibly have been chosen to avoid the overhead of dynamic dispatch. This solution does the same: the class sizes are unchanged and no vptr or vtbl is added. There are better designs for doing this that use composition instead of inheritance, or that separate the public interface from the private implementations. One example of the latter would be a non-virtual public interface that calls private virtual implementation methods, i.e. the Template pattern via the Non-Virtual Interface (NVI) idiom. ACKs for top commit: vasild: ACK 3a61fc5 Tree-SHA512: 9ff358ab0a6d010b8f053ad8303c6d4d061e62d9c3755a56b9c9f5eab855d02f02bee42acc77dfa0cbf4bb5cb775daa72d675e1560610a29bd285c46faa85ab7
2 parents 73a0d6d + 3a61fc5 commit 5057adf

File tree

5 files changed

+14
-23
lines changed

5 files changed

+14
-23
lines changed

src/chain.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

66
#include <chain.h>
7+
#include <tinyformat.h>
78
#include <util/time.h>
89

910
std::string CBlockFileInfo::ToString() const
1011
{
1112
return strprintf("CBlockFileInfo(blocks=%u, size=%u, heights=%u...%u, time=%s...%s)", nBlocks, nSize, nHeightFirst, nHeightLast, FormatISO8601Date(nTimeFirst), FormatISO8601Date(nTimeLast));
1213
}
1314

15+
std::string CBlockIndex::ToString() const
16+
{
17+
return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)",
18+
pprev, nHeight, hashMerkleRoot.ToString(), GetBlockHash().ToString());
19+
}
20+
1421
void CChain::SetTip(CBlockIndex *pindex) {
1522
if (pindex == nullptr) {
1623
vChain.clear();

src/chain.h

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include <flatfile.h>
1212
#include <primitives/block.h>
1313
#include <sync.h>
14-
#include <tinyformat.h>
1514
#include <uint256.h>
1615

1716
#include <vector>
@@ -263,6 +262,7 @@ class CBlockIndex
263262

264263
uint256 GetBlockHash() const
265264
{
265+
assert(phashBlock != nullptr);
266266
return *phashBlock;
267267
}
268268

@@ -301,13 +301,7 @@ class CBlockIndex
301301
return pbegin[(pend - pbegin) / 2];
302302
}
303303

304-
std::string ToString() const
305-
{
306-
return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)",
307-
pprev, nHeight,
308-
hashMerkleRoot.ToString(),
309-
GetBlockHash().ToString());
310-
}
304+
std::string ToString() const;
311305

312306
//! Check whether this block index entry is valid up to the passed validity level.
313307
bool IsValid(enum BlockStatus nUpTo = BLOCK_VALID_TRANSACTIONS) const
@@ -402,7 +396,7 @@ class CDiskBlockIndex : public CBlockIndex
402396
READWRITE(obj.nNonce);
403397
}
404398

405-
uint256 GetBlockHash() const
399+
uint256 ConstructBlockHash() const
406400
{
407401
CBlockHeader block;
408402
block.nVersion = nVersion;
@@ -414,16 +408,8 @@ class CDiskBlockIndex : public CBlockIndex
414408
return block.GetHash();
415409
}
416410

417-
418-
std::string ToString() const
419-
{
420-
std::string str = "CDiskBlockIndex(";
421-
str += CBlockIndex::ToString();
422-
str += strprintf("\n hashBlock=%s, hashPrev=%s)",
423-
GetBlockHash().ToString(),
424-
hashPrev.ToString());
425-
return str;
426-
}
411+
uint256 GetBlockHash() = delete;
412+
std::string ToString() = delete;
427413
};
428414

429415
/** An in-memory indexed chain of blocks. */

src/test/fuzz/chain.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,14 @@ FUZZ_TARGET(chain)
2323
disk_block_index->phashBlock = &zero;
2424
{
2525
LOCK(::cs_main);
26-
(void)disk_block_index->GetBlockHash();
26+
(void)disk_block_index->ConstructBlockHash();
2727
(void)disk_block_index->GetBlockPos();
2828
(void)disk_block_index->GetBlockTime();
2929
(void)disk_block_index->GetBlockTimeMax();
3030
(void)disk_block_index->GetMedianTimePast();
3131
(void)disk_block_index->GetUndoPos();
3232
(void)disk_block_index->HaveTxsDownloaded();
3333
(void)disk_block_index->IsValid();
34-
(void)disk_block_index->ToString();
3534
}
3635

3736
const CBlockHeader block_header = disk_block_index->GetBlockHeader();

src/txdb.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ bool CBlockTreeDB::LoadBlockIndexGuts(const Consensus::Params& consensusParams,
310310
CDiskBlockIndex diskindex;
311311
if (pcursor->GetValue(diskindex)) {
312312
// Construct block index object
313-
CBlockIndex* pindexNew = insertBlockIndex(diskindex.GetBlockHash());
313+
CBlockIndex* pindexNew = insertBlockIndex(diskindex.ConstructBlockHash());
314314
pindexNew->pprev = insertBlockIndex(diskindex.hashPrev);
315315
pindexNew->nHeight = diskindex.nHeight;
316316
pindexNew->nFile = diskindex.nFile;

src/validation.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2266,7 +2266,6 @@ bool CChainState::ConnectBlock(const CBlock& block, BlockValidationState& state,
22662266
m_blockman.m_dirty_blockindex.insert(pindex);
22672267
}
22682268

2269-
assert(pindex->phashBlock);
22702269
// add this block to the view's block chain
22712270
view.SetBestBlock(pindex->GetBlockHash());
22722271

0 commit comments

Comments
 (0)