Files
observer/crates/blackbeard-data/tests
grenadeandClaude Opus 5 0ce58094e4
deploy / build (push) Successful in 6m31s
deploy / deploy-web (push) Successful in 6s
deploy / deploy-api (push) Successful in 18s
feat(data): record what a reorg displaced
`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
2026-09-08 15:55:07 +03:00
..