ChainDB: add registerHeaderListener
Add a new entry on the ChainDB API:
registerHeaderListener :: (Header blk -> STM m ()) -> m ()
The caller registers an STM callback that runs on every successful
addBlockAsync add, after the block is written to the VolatileDB and
before chain selection runs on it. A subsystem that wants its own
state to track the chain database can write the update from the
listener; downstream observers then see the block on disk and the
listener's writes together.
Internals:
- New field cdbHeaderListeners :: StrictTVar m [HeaderListener m blk]
on ChainDbEnv. HeaderListener is a newtype around the callback so
the Generic-derived NoThunks instance on ChainDbEnv can pass over
the function via WHNF; same pattern as FollowerHandle and
ChainSelQueue in the same module.
- The call site in the ChainSelAddBlock arm of chainSelSync sits
between VolatileDB.putBlock and deliverWrittenToDisk. Order:
putBlock, atomically (runHeaderListeners hdr), deliverWrittenToDisk
True, chainSelectionForBlock. Any external thread woken by
deliverWrittenToDisk therefore sees the block on disk and the
listener's writes committed in the same step.
- All registered listeners fire inside a single atomically block.
Listener B sees listener A's writes; a throw from any listener
aborts the joint transaction (the in-flight add then completes with
FailedToAddBlock; the ChainSel thread survives).
- Listener bodies must be small and total. throwSTM and indefinite
retry inside the joint transaction stall addBlockAsync processing.
- Registrants that wire up after chainSelSync has already started may
miss blocks added in the registration window. Boot-time fencing is
the caller's responsibility.
No caller yet; the new entry is dormant.