Skip to content
Open
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
10 changes: 10 additions & 0 deletions src/checkpoints.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ namespace Checkpoints {
return NULL;
}

bool CheckBlock(const CCheckpointData& data, int nHeight, const uint256& hash)
{
const MapCheckpoints& checkpoints = data.mapCheckpoints;

MapCheckpoints::const_iterator i = checkpoints.find(nHeight);
if (i == checkpoints.end())
return true;
return hash == i->second;
}

static std::string FastSyncAnchorPayload(const CChainParams& chainparams, const CFastSyncAnchorData& anchor)
{
return strprintf("zclassic-fastsync-anchor-v1|%s|%d|%s",
Expand Down
5 changes: 5 additions & 0 deletions src/checkpoints.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ int GetTotalBlocksEstimate(const CCheckpointData& data);
//! Returns last CBlockIndex* in mapBlockIndex that is a checkpoint
CBlockIndex* GetLastCheckpoint(const CCheckpointData& data);

//! Returns false only if there is a checkpoint at nHeight and hash does not
//! match it. A block presented at a checkpoint height with a different hash is
//! a forgery and must be rejected (eclipse / bootstrap lock-in).
bool CheckBlock(const CCheckpointData& data, int nHeight, const uint256& hash);

double GuessVerificationProgress(const CCheckpointData& data, CBlockIndex* pindex, bool fSigchecks = true);

//! Validate the compiled fast-sync anchor against the checkpoint set and digest fields.
Expand Down
11 changes: 11 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4389,6 +4389,17 @@ bool ContextualCheckBlockHeader(const CBlockHeader& block, CValidationState& sta
CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(chainParams.Checkpoints());
if (pcheckpoint && nHeight < pcheckpoint->nHeight)
return state.DoS(100, error("%s: forked chain older than last checkpoint (height %d)", __func__, nHeight));

// Enforce the exact hash at checkpoint heights. A header presented at a
// checkpoint height with a different hash is a forgery. The forked-chain
// check above only covers heights strictly below the last checkpoint
// *present in mapBlockIndex*; this closes the gap at the checkpoint
// height itself and is independent of mapBlockIndex state, so it also
// protects a fresh/eclipsed node during bootstrap.
if (!Checkpoints::CheckBlock(chainParams.Checkpoints(), nHeight, hash))
return state.DoS(100, error("%s: rejected by checkpoint lock-in at height %d (hash %s)",
__func__, nHeight, hash.ToString()),
REJECT_CHECKPOINT, "bad-fork-checkpoint");
}

// Reject block.nVersion < 4 blocks
Expand Down
22 changes: 22 additions & 0 deletions src/test/Checkpoints_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,26 @@ BOOST_AUTO_TEST_CASE(fast_sync_anchor_negative_branches)
}
}

BOOST_AUTO_TEST_CASE(checkpoint_hash_lockin)
{
const uint256 hashA = uint256S("0x00000000000000000000000000000000000000000000000000000000000000aa");
const uint256 hashB = uint256S("0x00000000000000000000000000000000000000000000000000000000000000bb");

CCheckpointData data{};
data.mapCheckpoints[30000] = hashA;
data.mapCheckpoints[160000] = hashB;

// No checkpoint at this height -> always accepted, regardless of hash.
BOOST_CHECK(Checkpoints::CheckBlock(data, 12345, hashA));
BOOST_CHECK(Checkpoints::CheckBlock(data, 12345, hashB));

// Correct hash at a checkpoint height -> accepted.
BOOST_CHECK(Checkpoints::CheckBlock(data, 30000, hashA));
BOOST_CHECK(Checkpoints::CheckBlock(data, 160000, hashB));

// Wrong hash at a checkpoint height -> rejected (the forgery case #2 closes).
BOOST_CHECK(!Checkpoints::CheckBlock(data, 30000, hashB));
BOOST_CHECK(!Checkpoints::CheckBlock(data, 160000, hashA));
}

BOOST_AUTO_TEST_SUITE_END()
Loading