UPDATE: the concern mentioned below doesn’t exist, see comments below.
Current logic
Since #3370, our logic for deciding which chain is active has been:
- Among chains for which all transactions have been received, and are not known to contain invalid blocks…
- Pick the one with the highest cumulative work.
- If there are multiple such chaintips, pick the one for which the tip was received first (full block data, not just the header)
The reason for this last condition is protecting against block withholding. If an attacker manages to mine a block, quickly spread its header around the network but withhold the full block data, they can wait until a competing miner finds their block, at which point they broadcast the full block data. If rule (3) would use “earliest received header” as condition, they’d be able to retroactively make their block win.
Remaining issue
It appears to me however that strictly speaking, the tie-breaker (3) doesn’t fully solve this. It is still possible to play the same withholding game, but with an extra block depth:
0graph RL
1 a1["A"];
2 b1["B"];
3 c1["C"];
4 b2["B'"];
5 c2["C'"];
6 c1 --> b1 --> a1;
7 c2 --> b2 --> a1;
The attacker mines blocks B and C in secret, broadcasts the header of B, and the full block data of C. Other miners cannot build on top of this (except empty blocks). As soon as the rest of the network catches up (by mining B’ and C’), the attacker broadcasts B. With the current logic, the attacker’s A-B-C chain will become the active chain.
I consider this very hard to pull off, as it requires a full block advantage already, and headers do not propagate through the network on their own. Still, it deserves addressing.
Solution
This pull requests replaces the tie-breaker (3) “earliest received tip” with “earliest activateable chain”, as in: among the acceptable chains according to (2), pick the one for which the entire chain was received first.
Abstractly, the condition can be described as:
- Assign with each block a “timedata”, the sorted high to low list of all timestamps at which all the blocks’ ancestors were received.
- Among the (2) acceptable chaintips, pick the one which has the lowest timedata (comparing lexicographically after sorting).
In practice this is implemented by finding the maximum sequence value in each chain since the fork point between them, and then picking the one with the lowest maximum.