ZooBC wiki
Proof of Participation

Fork choice and cumulative difficulty

You are looking at an old version of this page: revision #29 by zoobc.how, 2026-10-26 20:39 UTC. See the current version

Sometimes two versions of the chain compete for a while, for example when the network is split for some minutes. Every ZooBC node then follows the branch with the greater cumulative difficulty: a running total of block weights in which a block from an early-drawn producer weighs more than a late one. The rule is the same arithmetic on every node, so the network converges without anyone choosing a winner.

Heavier, not longer

Each block adds to the running total an amount that shrinks with how far down the height's draw its producer was. In its simplest form, a block adds 1,000,000 divided by the producer's rank in the draw: a block from the first producer drawn adds 1,000,000, one from the third adds about 333,333.

A chain of on-time blocks therefore outweighs a chain whose producers kept missing their turns, even when the second chain is longer. Three first-round blocks weigh 3,000,000; four third-round blocks weigh about 1,333,332.

A fork: the upper branch has three blocks of 1,000,000 (total 3,000,000), the lower branch four blocks of 333,333 (total 1,333,332). The scale tips to the upper branch: heavier wins, not longer.
A fork: the upper branch has three blocks of 1,000,000 (total 3,000,000), the lower branch four blocks of 333,333 (total 1,333,332). The scale tips to the upper branch: heavier wins, not longer.

The alternatives would not work. Counting transactions would let a producer pad its block to win. "First seen" differs from node to node because of network delays, so nodes could stay split for ever. And there is no authority that marks a branch as official.

The exact weight of a block

From version 0.5.8 the increment of a block is:

increment = floor(1,000,000 × |S| / (k × |A|))
  • S is the draw set at the previous block: the active nodes minus the suspended ones.
  • A is the full active registry at the previous block. The share |S| / |A| is never more than 1.
  • k counts the draws up to the producer's last appearance in the rounds up to the block's round: 1 for the first-drawn producer stamping on time, 3 for a producer named by the third draw. (If the producer is not found there, its first appearance within 2 × n draws is used, and failing that a position in hash order.)

Two floors stop a block from claiming more than it deserves. k is never less than the round the block's timestamp falls in, so a late stamp cannot keep an early round's weight (see rounds, timing and windows). And for the producer of the previous block, k is never less than its demoted round, 10 (see priority rounds, empty blocks and the previous producer).

The draw-set share matters when the network splits. If each side of a split suspends the nodes it cannot hear from, both sides would otherwise add 1,000,000 per on-time block and look equally heavy. With the share, a side running on 3 of 11 seats adds at most 272,727 per block, against 727,272 for the side with 8. The side holding more seats wins.

The increment formula with |S| = 8, |A| = 11, k = 1 gives 727,272; for a split 11-node chain, the side with 8 seats adds 727,272 per block and the side with 3 seats 272,727.
The increment formula with |S| = 8, |A| = 11, k = 1 gives 727,272; for a split 11-node chain, the side with 8 seats adds 727,272 per block and the side with 3 seats 272,727.

Every block carries its cumulative difficulty as a decimal string. Every validator recomputes it and refuses a block that states it wrongly. Two totals are compared by length first, then digit by digit.

The participation score plays no part in any of this: the weight reads draw positions and the draw set, not scores.

Exact ties

Exact ties happen, for example when two branches of the same length are both made of on-time, first-round blocks. From version 0.5.8 the tip block hashes of the two branches are compared as bytes, and the lower hash wins. A node switches to an equal-weight branch whose tip hash is lower than its own, keeps its own otherwise, and asks nobody. Every node applies the same comparison to the same two hashes, so the whole network converges at once.

Weighing a branch before switching

A peer's claim that its branch is heavier proves nothing by itself. A node weighs a competing branch before it touches its own chain:

  1. It finds the last block the two chains share.
  2. It fetches the branch after that block in pages, checking every header signature and the links between blocks.
  3. It recomputes each block's weight, replaying that branch's own registry and suspensions from the common block onward, since they can differ on the far side of a split.
  4. It stops as soon as the branch is heavier than its own chain, and only then rolls back and applies it. A branch that ends lighter, or fails to overtake within a bounded number of blocks, is refused with nothing rolled back.

A peer whose claimed difficulty differs from the recomputed value is recorded as having lied.

Rollbacks have hard limits: never more than 720 blocks (3 hours) on mainnet, and never below the last checkpoint co-signed on the spine chain. See finality and rollback.

Stale nodes and peer polls

A node whose tip is older than 7,200 seconds (2 hours) does not trust whichever peer claims to be ahead: it follows the tip that at least two thirds of a sample of registered peers report. Other peer polls a node may run, to confirm a heavier branch or check its health, are local policy, not consensus.

See also