§ 11 · Blockchain
Checkpoints
Twelve block hashes written into the software
On this page 6 sections
Every block hash depends on the block before it. So if you know the hash of block 120,000, you know, in effect, the entire chain up to that point: change any earlier byte and that hash would come out different.
Pearl’s node software ships with such hashes written in. They are checkpointsCheckpoint: A block hash built into the node software at a fixed height. Pearl has one every 10,000 blocks so far, and nodes refuse any chain that disagrees with them.: one known block hash every 10,000 blocks. A node checks the chain it downloads against them and refuses anything that disagrees.
Try it
Edit an old block in this little chain and watch every later hash change. A checkpoint is simply one of those later hashes, written down in advance.
What are Pearl’s checkpoints?
Twelve pairs of height and block hash, in pearld v1.5.2:
| Height | Block hash |
|---|---|
| 10,000 | a3c196fb1c3f7837bbebd69b98fc451d88d48083af98c318c78486d7f1888490 |
| 20,000 | 513270b25c6d538f38b26ecdc014f2a809639ec7ebd3c09e4685675311420273 |
| 30,000 | fd61132c2ad6c22fa48a8df483bc6b885c12ceeb3fe0a9fa55eb5beb849fac7b |
| 40,000 | 35aac0e2a1e7f2d54924d584cf423b56f0d72d6770ee8365dc1667dbb5b5b320 |
| 50,000 | 608f32e5390b2ae964c986a53e1be10ba4a640f3ccecf96d6b7838e06cb517ff |
| 60,000 | 5a7f0590c3a89099b2d0f1509d2186c73bc3d8e2e4a60e7ce4ec236856900dad |
| 70,000 | 281a3469d55226bb9e1b3ccee06eeb923177817b4cdead75131074b171b215c1 |
| 80,000 | 69d40caca39b3c04ee55589d13cd1c5c546a3eb8712a3367840c5f67ab707cfc |
| 90,000 | 0738b30bb353820a4f97331d805d134de4321a5fc5c8b2955988e7291a0c8562 |
| 100,000 | ca8d5f604a9021e538f31472d9a9641c7bc46a6b8d591748e2869429571edbfb |
| 110,000 | c119b81da722428e61dcda4ec1d6a2a56e6e40acb29592d11081538ab3d50630 |
| 120,000 | a5f57e441de1da43a27b4bee471c7a7bdd8e36664050385bf0a1b760d2a84b7d |
Each hash was checked against Pearl’s public block explorer, and all twelve match. For block 120,000, the research behind this site went further: it fetched the raw block and hashed its header with Pearl’s own code, which gives exactly the checkpoint hash.
When is a checkpoint added?
Once a block is deep enough that nobody expects it to change. The code’s rule of thumb is CheckpointConfirmations = 2016: a candidate must have at least 2,016 blocks on top of it, about four and a half days at the target pace. Then a developer adds the next multiple of 10,000 in a new release.
So the list grows with each release, and older releases simply know fewer checkpoints.
What does a node do with them?
Several things, all in pearld v1.5.2:
-
Match
A block at a checkpoint height must have exactly the checkpoint’s hash, or it fails with
ErrBadCheckpoint, whatever its work. -
No forks below the latest
A block that would fork the chain below the latest checkpoint (block 120,000) is refused with
ErrForkTooOld. The code comment’s reason: such blocks build on old, easier difficulty and could be used to waste memory and disk. It also means no reorganization can reach below that block. -
Skip script checks below it
While catching up, scripts and signatures at or below the latest checkpoint aren’t run: the checkpoint’s hash already pins every transaction through the Merkle roots. Running scripts is the slowest part of checking a block, so this is a big speed-up. Each block’s certificate is still verified.
-
Sync headers in checkpoint-sized steps
A new node downloads headers, with their certificates, up to the next checkpoint before it fetches the blocks. See messages.
-
Sanity-check claimed difficulty
Before fully checking an unknown block, a node compares its bits with the last checkpoint’s: over a time D, the target can’t have grown by more than (87/32) per started week. See WTEMA.
Pearl’s DNS seeders use checkpoints too. They only hand out addresses of nodes whose chain reaches at least the latest checkpoint, which weeds out nodes stuck on an old, pre-fork chain. See peers and seeds.
Why one every 10,000 blocks?
The comment next to the list gives the reason: “denser checkpoints cap how much headers-first sync progress a peer disconnect can throw away.” A new node syncs headers one checkpoint-sized stretch at a time. If the peer it’s syncing from drops out halfway, the node has at most one stretch to redo.
On Pearl that matters more than on Bitcoin, because each header travels with its roughly 60 KB certificate, and the node verifies every one.
Doesn’t that mean trusting the developers?
A little, and it’s worth being clear how much.
The checkpoints are in the open source code, and anyone can compare them with the chain, as this page’s research did. A checkpoint can’t make an invalid block valid: blocks below it still need valid certificates and must link up correctly. What it does do is freeze history: a node won’t consider any chain that disagrees with a checkpoint or forks below the latest one, however much work it has. And below the latest checkpoint, transaction scripts aren’t re-checked during sync.
So you trust that the block hashes in the release were the real chain when they were written in, something you can check yourself, in exchange for faster syncing and protection against deep rewrites.
Make a guess
An attacker secretly mines a chain from block 115,000 and, by October 2026, it has more total work than the real chain. Can they reorganize Pearl nodes onto it?
Think of your answer first, then check it.
Answer No, not nodes running v1.5.2.
Their chain forks at 115,000, below the latest checkpoint, 120,000. Up-to-date nodes refuse its blocks with
ErrForkTooOld, and its block 120,000 wouldn’t match the checkpoint hash anyway. Above the latest checkpoint, though,
only the most-work rule protects the chain.
Code: matching checkpoints and refusing old forks
From validate.go (node/blockchain/validate.go:705-726 at v1.5.2, on GitHub):
blockHash := header.BlockHash()
if !c.VerifyCheckpoint(blockHeight, &blockHash) {
return ruleError(ErrBadCheckpoint, str)
}
checkpointNode, err := c.FindPreviousCheckpoint()
if checkpointNode != nil && blockHeight < checkpointNode.Height() {
return ruleError(ErrForkTooOld, str)
} The list itself is in params.go (node/chaincfg/params.go:373-390 at v1.5.2, on GitHub), and the script
skipping in validate.go (node/blockchain/validate.go:1086-1096 at v1.5.2, on GitHub):
checkpoint := b.LatestCheckpoint()
runScripts := true
if checkpoint != nil && node.height <= checkpoint.Height {
runScripts = false
}