§ 11

§ 11 · Blockchain

Forks and upgrades

Four rule changes, each switched on at a block height fixed in advance

A blockchain’s rules live in software that many independent people run. Changing a rule means getting all of them to change at the same moment, or the network splits into nodes that accept a block and nodes that don’t.

Pearl’s answer is simple: pick a block height, write it into the next release, and give everyone time to upgrade. When the chain reaches that height, the new rule applies, on every up-to-date node at once. Four rules have changed this way so far.

What is a fork?

The word has two meanings in blockchains.

  • A chain split: two blocks on the same parent, for a moment. That’s ordinary and resolves itself; see the most-work chain.
  • A rule change: a new version of the rules. That’s this page.

Rule changes come in two kinds:

Soft fork Hard fork
What changes The rules get stricter Something new is allowed
Old nodes Still accept every new block Reject new blocks and fall behind
Who must upgrade Miners, so they don’t make blocks the new rules reject Everyone: nodes, miners, pools
On Pearl Dense-only (91,630), rank penalty (96,251) MoE (71,935), salted noise seeds (99,000)

How does Pearl schedule a change?

With a number in the chain parameters. Each fork has a height, and helper functions like IsMoEForkActive(height) answer “does this rule apply here?” Nodes check the certificate rules for every block against its height. The software even refuses to start if the rank-penalty or salted-seed height is set before the MoE height, since both rules assume V2 certificates exist.

Miners don’t vote. The header’s version field still carries Bitcoin’s BIP-9 version bits, but Pearl’s mainnet defines only three test dummy deployments. Blocks from about 2,100 to 100,799 carry version 0x20400000 (bit 22 set), most likely because of one of those dummies; either way it means nothing. See version.

What are the four forks?

certificate V1V2V3MoE (hard) 71,935salted seed (hard) 99,000dense-only (soft) 91,630rank penalty (soft) 96,2510130,82727 Apr 202620 Oct 2026
Pearl's mainnet rule changes on a height axis, with the certificate version each stretch of the chain must carry. Heights to scale; the tip shown is the 20 October 2026 snapshot.
HeightActivatedForkKindWhat changed
71,9352026-06-12MoE hard forkhardBlocks must carry a V2 certificate, which adds support for Mixture-of-Experts proofs.
91,6302026-07-23Dense-only soft forksoftMixture-of-Experts proofs are rejected again; only dense proofs count.
96,2512026-08-06Rank-penalty soft forksoftNoise rank must be at least 128, and a higher rank makes the winning bar stricter in proportion.
99,0002026-08-11Salted noise-seed hard forkhardBlocks must carry a V3 certificate: the noise seeds now also commit to the matrix sizes.

All four touch the mining certificate, never transactions or the header layout.

MoE hard fork, block 71,935

From this height, blocks must carry a V2 certificate instead of V1. V2 made the certificate’s public data variable in length, so it could describe mixture-of-experts jobs: the matrix multiplications inside MoE layers of large language models, where each token is routed to a few “expert” weight matrices.

Dense proofs, the ordinary kind, stayed valid in V2, so hashing miners didn’t need to change. Nodes and the software that turns a share into a certificate did: an old node rejects a V2 certificate. That’s what made it hard.

Dense-only soft fork, block 91,630

From this height, a V2 certificate must be dense: public data longer than 164 bytes (an MoE proof) is rejected. The set of valid blocks only shrank, so old nodes kept accepting everything, a soft fork. The height moved from 91,600 to 91,630 during development, after a fix for empty block templates being mistaken for MoE.

Why MoE proofs were switched off again hasn’t been published. MoE support continues in Pearl’s separate FP8 work, which is not live.

Rank-penalty soft fork, block 96,251

From this height, the noise rank must be at least 128, and a certificate with a higher rank faces a stricter bar, scaled by 128 ÷ rank. Pearl’s own explanation: a proof’s ticket bar “is scaled by the work one attempt costs, and the noise rank divides that work, so mining at a larger rank buys a proportionally easier bound for the same effort.” The penalty cancels that advantage. Details are on rank penalty.

Salted noise-seed hard fork, block 99,000

From this height, blocks must carry a V3 certificate. The bytes are laid out exactly like V2; what changed is how the noise seeds are derived. Each matrix fingerprint is now salted with the matrix size the miner claims, so one committed matrix can no longer be “read” under several shapes to collect extra tickets, a form of work amplification.

Because the noise changed, miners had to upgrade too this time. The height moved twice in development, from 98,830 to 98,900 to 99,000. A related security advisory is referenced in Pearl’s code history but not public; see noise seeds.

Can you see the forks on chain?

Yes, in the very first bytes of each raw block, which start with the certificate version:

Block First four bytes Certificate
71,934 01000000 V1
71,935 02000000 V2
98,999 02000000 V2
99,000 03000000 V3

Here is block 1’s certificate, a V1, and block 130,827’s, a V3:

Block 1 · certificate 236 bytes
01000000 2697a64537f638c2a699e9bb029b8792ae641853e7dff770d1391576a45198a0 00100000 8000 0000 070103030000 000103010103 0000 0000 00000000000000000000000000000000000000000000000000000000 f979a482b148ad659aed4fe23f13724797538eeda9e89d92bd2f575bd6479bc7 a8eaf0d7b08f0a6a5fd0b6adbf08cdc2e0e649035047d1a373a5d7d12641047f 7e0c03ed79c5602451370ad176151ac5568124a98e3d3dc95576534f07000000 00800000 00700000 20780000 00500000 a8e80000 1212160203075344cee1978085e444c0cf9c5720954418606692ea0b7b1ae579
Field Bytes Value
Certificate version certificate.version 4 @0 1 u32 LE. V1 before block 71,935, V2 until 98,999, V3 (salted noise seeds) from 99,000. 0 = no certificate.
Block hash certificate.blockHash 32 @4 a09851a4761539d170f7dfe7531864ae92879b02bbe999a6c238f63745a69726 SHA256d of the 108-byte header this certificate proves (stored byte-reversed); must equal the header’s hash.
Common dimension k certificate.publicData.k 4 @36 4096 u32 LE: the shared dimension of A (m×k) and B (k×n).
Noise rank r certificate.publicData.rank 2 @40 128 u16 LE: rank of the low-rank noise; must be ≥ 128 since block 96,251.
MMA type certificate.publicData.mmaType 2 @42 0 0 = Int7 × Int7 → Int32.
Rows pattern certificate.publicData.rowsPattern 6 @44 [0,8,64,72,128,136,192,200] (h=8) Periodic row offsets of the hash tile, stored as (factor−1, length−1) × 3.
Columns pattern certificate.publicData.colsPattern 6 @50 [0,1,8,9,32,33,40,41,64,65,72,73,96,97,104,105] (w=16) Periodic column offsets of the hash tile.
Experts e certificate.publicData.experts 2 @56 0 Number of MoE experts; 0 = dense (MoE is invalid on mainnet since 91,630).
Top-k certificate.publicData.topK 2 @58 0 0 for dense jobs.
Reserved certificate.publicData.reserved 28 @60 00000000000000000000000000000000000000000000000000000000 Must be zero.
hash_A certificate.publicData.hashA 32 @88 f979a482b148ad659aed4fe23f13724797538eeda9e89d92bd2f575bd6479bc7 Keyed-BLAKE3 Merkle root of matrix A (key = job key).
hash_B certificate.publicData.hashB 32 @120 a8eaf0d7b08f0a6a5fd0b6adbf08cdc2e0e649035047d1a373a5d7d12641047f Keyed-BLAKE3 Merkle root of Bᵀ (B column-major).
hash_jackpot certificate.publicData.hashJackpot 32 @152 7e0c03ed79c5602451370ad176151ac5568124a98e3d3dc95576534f07000000 The winning ticket: BLAKE3 keyed with seed_A over the tile transcript; read as a little-endian number and compared with the bound.
m (rows of A) certificate.publicData.m 4 @184 32768 u32 LE; bound into the noise seeds since certificate V3.
n (columns of B) certificate.publicData.n 4 @188 28672 u32 LE; bound into the noise seeds since certificate V3.
Tile row offset certificate.publicData.tRows 4 @192 30752 Smallest row index of the winning hash tile.
Tile column offset certificate.publicData.tCols 4 @196 20480 Smallest column index of the winning hash tile.
Proof length certificate.proofLength 4 @200 59560 u32 LE; at most 60,000 bytes.
ZK proof (first 32 of 59,560 bytes) certificate.proof 32 @204 59,560 bytes, collapsed pow_bits(3) ‖ rate_bits(3) ‖ zeta(16) ‖ Plonky2 proof: a STARK wrapped in two Plonky2 recursions. Not covered by the block hash.
ProofCommitment check
SHA256d(version ‖ public data) matches the header ✓
Certificate
V1 · 59,764 bytes
Proof
59,560 bytes (99.7%)
The proof itself is 59,560 bytes; only its first 32 are shown.
Block 130,827 · certificate 240 bytes
03000000 b9456d3c0623dc385a879a4e1ff9a26d73118c78c122e644a4b139bfc1f2f21d a4000000 00200000 8000 0000 070101030000 00010f070000 0000 0000 00000000000000000000000000000000000000000000000000000000 9e1adb4617d7d05111fe2a7e074f81738df4e2181b966ac48f66289b1cab5d4a d1b42f82cfe36192a36ed37be30a6f9f75e7bca372449aec615e7d0721dc06ef 7540013c2866ec7f6b076e1445f907276b9f27ede2b298603604000000000000 00000200 00000200 84500100 12080100 a8e80000 1212160103073c375044e471479d284e49400353a81901b092b4245ce3eb7b45
Field Bytes Value
Certificate version certificate.version 4 @0 3 u32 LE. V1 before block 71,935, V2 until 98,999, V3 (salted noise seeds) from 99,000. 0 = no certificate.
Block hash certificate.blockHash 32 @4 1df2f2c1bf39b1a444e622c1788c11736da2f91f4e9a875a38dc23063c6d45b9 SHA256d of the 108-byte header this certificate proves (stored byte-reversed); must equal the header’s hash.
Public data length certificate.publicDataLength 4 @36 164 164 for dense jobs; longer means MoE (invalid since 91,630).
Common dimension k certificate.publicData.k 4 @40 8192 u32 LE: the shared dimension of A (m×k) and B (k×n).
Noise rank r certificate.publicData.rank 2 @44 128 u16 LE: rank of the low-rank noise; must be ≥ 128 since block 96,251.
MMA type certificate.publicData.mmaType 2 @46 0 0 = Int7 × Int7 → Int32.
Rows pattern certificate.publicData.rowsPattern 6 @48 [0,8,32,40,64,72,96,104] (h=8) Periodic row offsets of the hash tile, stored as (factor−1, length−1) × 3.
Columns pattern certificate.publicData.colsPattern 6 @54 [0,1,32,33,64,65,96,97,128,129,160,161,192,193,224,225] (w=16) Periodic column offsets of the hash tile.
Experts e certificate.publicData.experts 2 @60 0 Number of MoE experts; 0 = dense (MoE is invalid on mainnet since 91,630).
Top-k certificate.publicData.topK 2 @62 0 0 for dense jobs.
Reserved certificate.publicData.reserved 28 @64 00000000000000000000000000000000000000000000000000000000 Must be zero.
hash_A certificate.publicData.hashA 32 @92 9e1adb4617d7d05111fe2a7e074f81738df4e2181b966ac48f66289b1cab5d4a Keyed-BLAKE3 Merkle root of matrix A (key = job key).
hash_B certificate.publicData.hashB 32 @124 d1b42f82cfe36192a36ed37be30a6f9f75e7bca372449aec615e7d0721dc06ef Keyed-BLAKE3 Merkle root of Bᵀ (B column-major).
hash_jackpot certificate.publicData.hashJackpot 32 @156 7540013c2866ec7f6b076e1445f907276b9f27ede2b298603604000000000000 The winning ticket: BLAKE3 keyed with seed_A over the tile transcript; read as a little-endian number and compared with the bound.
m (rows of A) certificate.publicData.m 4 @188 131072 u32 LE; bound into the noise seeds since certificate V3.
n (columns of B) certificate.publicData.n 4 @192 131072 u32 LE; bound into the noise seeds since certificate V3.
Tile row offset certificate.publicData.tRows 4 @196 86148 Smallest row index of the winning hash tile.
Tile column offset certificate.publicData.tCols 4 @200 67602 Smallest column index of the winning hash tile.
Proof length certificate.proofLength 4 @204 59560 u32 LE; at most 60,000 bytes.
ZK proof (first 32 of 59,560 bytes) certificate.proof 32 @208 59,560 bytes, collapsed pow_bits(3) ‖ rate_bits(3) ‖ zeta(16) ‖ Plonky2 proof: a STARK wrapped in two Plonky2 recursions. Not covered by the block hash.
ProofCommitment check
SHA256d(version ‖ public data) matches the header ✓
Certificate
V3 · 59,768 bytes
Proof
59,560 bytes (99.7%)
The proof itself is 59,560 bytes; only its first 32 are shown.

Are any other changes scheduled?

Not in v1.5.2. A search of the code finds no other height-gated rules, and the v1.5.x releases add no new fork height. Two smaller changes in those releases apply without a height: OP_CHECKXMSSSIG now fails on a wrong-length key or signature chunk, and spending an output created later in the same block is rejected, as in Bitcoin Core.

Pearl Research Labs is working on a new mining scheme (FP8) with a certificate V4. It is not live; if it ships, it will be another fork with its own height.

Make a guess

A pool still runs node software from before 71,935 when the MoE fork arrives. What happens to the blocks its miners find?

Think of your answer first, then check it.

Answer Every up-to-date node rejects them.

At 71,935 and above, up-to-date nodes require a V2 certificate. The old software keeps building V1 blocks, which everyone else rejects, so the pool’s blocks never join the main chain and its rewards are lost. That’s why the upgrade guides told node operators to upgrade well before the fork height.

Code: fork heights on every network

From params.go (node/chaincfg/params.go:364-371 at v1.5.2, on GitHub) and the testnet sections of the same file:

Fork Mainnet Testnet Testnet2 Regtest
MoE 71,935 1 54,869 1
Dense-only 91,630 28,262 80,051 off
Rank penalty 96,251 36,761 80,627 1
Salted seed 99,000 38,648 83,109 1

Simnet sets MoE and salted seed to 1. The certificate version a height needs comes from RequiredCertVersion (node/chaincfg/params.go:326-338 at v1.5.2, on GitHub):

node/chaincfg/params.go Go
func (p *Params) RequiredCertVersion(height int32) wire.CertificateVersion {
	if p.IsSaltedSeedForkActive(height) {
		return wire.CertificateVersionV3
	}
	if p.IsMoEForkActive(height) {
		return wire.CertificateVersionV2
	}
	return wire.CertificateVersionV1
}

CheckCertificateRules (node/blockchain/validate.go:508-559 at v1.5.2, on GitHub) enforces it, along with dense-only and the rank penalty, for every block and every synced header.

The codex

Suggested

Up and down arrows move through the results, Enter opens one, Escape closes. Anywhere on the site, slash or Command K opens this search, and the square brackets [ and ] go to the previous and next page.