`block` is keyed on (chain, height) so a replacement overwrites rather than accumulates — a schema keyed on the hash would inflate the losing fork's author forever. The cost is that the losing block vanishes without a trace, and with it any record that the height was ever contested. `block_displacement` is that trace: one row per displacement, holding the whole row that was about to be overwritten. It cannot be reconstructed later. `blocks-pruning` defaults to archive-canonical, which discards non-canonical bodies once finality passes them — about a hundred blocks behind the tip on Planck — and difficulty is a state read against a 256-block default. Minutes after a reorg the chain itself can no longer answer for the block, so what is captured at the moment of the swap is all there will ever be. That is why the row holds the author and the difficulty rather than just the hash: the author is the interesting field, it says who lost the race, and it costs a table that gains a row only when the chain forks. The capture is a CTE ahead of the upsert, in the same statement. Every data-modifying CTE sees the same snapshot, so it reads the pre-update row even though the insert below it is replacing that row in the same breath, and a failure rolls back both — which two statements without a transaction would not. A trigger would do the same thing invisibly to anyone reading the query. Nothing reads the table yet, deliberately. The read shape belongs with the block route that will use it; what matters now is that the history exists by the time that lands, which it cannot do retroactively. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Jp6a8EDar9ueEhAxzep4V5