`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
47 lines
2.4 KiB
SQL
47 lines
2.4 KiB
SQL
-- What a reorg overwrote.
|
|
--
|
|
-- `block` is keyed on (chain, height) precisely so a replacement overwrites
|
|
-- rather than accumulates — a schema keyed on the hash would inflate the losing
|
|
-- fork's author forever. The cost of that choice is that the losing block is
|
|
-- gone without a trace, and with it the only record that the height was ever
|
|
-- contested. This table is that trace: one row per displacement, holding
|
|
-- exactly what was about to be overwritten.
|
|
--
|
|
-- It is not an attempt to keep the orphan itself. The node will not serve one
|
|
-- for long — `blocks-pruning` defaults to `archive-canonical`, which discards
|
|
-- non-canonical bodies once finality passes them, and Planck finalises about a
|
|
-- hundred blocks behind the tip. Difficulty is worse: it is a state read, and
|
|
-- state defaults to 256 blocks. So a few minutes after the reorg the chain
|
|
-- itself can no longer answer for the block, and what is written here is all
|
|
-- there will ever be.
|
|
--
|
|
-- Which is why it holds the whole displaced row rather than just its hash. The
|
|
-- author is the interesting field — it says who lost the race — and it costs
|
|
-- eight columns of a table that gains a row only when the chain forks.
|
|
create table block_displacement (
|
|
chain text not null references chain (id) on delete cascade,
|
|
height bigint not null,
|
|
-- The displaced block's hash. Addressable: a block route keyed on the hash
|
|
-- can serve this even though `block` no longer mentions it.
|
|
hash text not null,
|
|
miner text not null,
|
|
authored_at timestamptz,
|
|
observed_at timestamptz not null,
|
|
difficulty numeric,
|
|
at_tip boolean not null,
|
|
-- When the replacement arrived. On the same hash being displaced twice —
|
|
-- a chain that reorgs back and forth across the same pair — this is the
|
|
-- most recent time, and every other column is immutable for a given hash.
|
|
displaced_at timestamptz not null default now(),
|
|
primary key (chain, height, hash)
|
|
);
|
|
|
|
-- "What else has held this height?", for the block page's replaced-by banner.
|
|
create index block_displacement_chain_height_idx
|
|
on block_displacement (chain, height desc);
|
|
|
|
-- "Is this hash a block we once recorded?" — the lookup a hash-addressed route
|
|
-- makes, which the primary key cannot serve without knowing the height first.
|
|
create index block_displacement_chain_hash_idx
|
|
on block_displacement (chain, hash);
|