fix: harden batch ordering, proposer requeue, and boot recovery - #123
Conversation
e2a2741 to
c373933
Compare
- never rewind the epoch: drain_epoch treated any change as a forward crossing, so a crash-replay of an already-passed epoch's output drained the live parked set and moved the epoch backward (a watermark with a backward path); a stale-epoch drain is now a warned no-op, pinned by drain_epoch_never_rewinds_to_an_already_passed_epoch - fail closed and uncap the boot history walk: under the default long-read cap a slow cold boot aborted the txn mid-walk and unwrap_or_default booted with EMPTY watermarks, silently disabling gap detection (the after_round starvation class); the walk opts out of the cap like the catch-up accumulator, fails loud, and decodes only the leader epoch and payload digests via the zero-copy bcs projection - persist parked batches by digest (ParkedRef), not by value: a committed batch's Batches row outlives the reboot, so bodies only duplicated bytes in the blob; the restart read reloads them in short raw-read txns; snapshot under the lock, write OUTSIDE it (the persist-starvation class); the authority map becomes BTreeMap for deterministic persisted bytes and recovery iteration - backwards compatible: a legacy by-value blob fails the compact decode and decodes via the fallback, pinned by stored_and_legacy_ordering_blobs_are_distinguishable_by_decode
… they justified - the orphan-batch rescue was NodeBatchesCache's only reader; since its deletion every seal still paid a write, the proposer a per-commit cleanup txn, and the boundary a table clear, all for rows nothing reads - the epoch-boundary-drop bool existed solely to skip that cleanup for boundary headers, so committed_certificates and committed_own_headers now carry certificates and rounds plain; the proposer filter-then-maps its own committed rounds - the table stays declared (stable column-family set) and is still cleared on foreign-DB sanitization to purge rows written by older binaries - pinned: the el-to-cl seal test asserts no NodeBatchesCache row is written
…ted-round horizon - GC keyed the eviction horizon on self.round and silently dropped the evicted headers' digests: those digests are quorum'd and seq-consumed, so a drop gaps the per-authority seq stream permanently on peers (the in-flight TTL only re-seals the txs under a NEW seq); the horizon now keys on the monotonic committed round - commits trail the proposal frontier, so a header in the lag window is still committable - and eviction requeues the digests FIFO-ahead of the queue (RequeueReason::GcEvict) - the foreign-commit fallback retransmitted EVERY proposed header, fresh ones included, manufacturing duplicate commits for headers whose certificates were still collecting votes; it now splits at a horizon FALLBACK_REQUEUE_GRACE_ROUNDS below the commit round, so ordinary commit lag never triggers it and fresh headers stay proposed (RequeueReason::CommitLag) - BatchStage::RequeuedInProposer joins the tracker lifecycle - red-proven: fallback_requeue_forgives_ordinary_commit_lag fails at grace 0, digests_survive_gc_advance_and_later_round_commit fails when the requeue drops; push_digest_never_drops and fallback_requeue_stops_at_the_commit_round pin the never-drop and fresh-header contracts
- run_epoch: an external consensus shutdown arriving DURING the running select resolved through the task-manager join arm, where a critical task exiting Ok in response was misclassified as a crash and killed the node - was_externally_shutdown only samples the state before the select; a biased epoch-shutdown arm above the boundary arm now resolves it as a clean NodeShutdown - fence the batch builder on a single worker: the node-scoped in-flight tracker is shared by every worker pool, so a second worker's boundary clear or reconcile would wipe the sibling's marks; ensure! names the assumption instead of corrupting silently - promote the batches/digests parity check to a release assert: debug_assert_eq compiled out and the raw digest indexing then aborted messageless on a mismatch Hardening: the shutdown race and the multi-worker wipe need multi-task fault injection to demonstrate red, so these land without failing-first tests.
- mark force-drained parked batches in the tracker (ForceDrained; pre-fork boundary path only, the fork discards instead) so an out-of-order boundary execution is attributable - demote AlreadyImported gossip-admission errors to debug: overlapping observer forwards make it the common outcome under load, and warn buried the submissions that did fail - enable the txpool RPC module (txpool_content/txpool_status) for pool inspection
c373933 to
e460f8f
Compare
|
Claude finished @bronxyz's task in 2m 6s —— View job PR Review:
|
…ting - boot recovery already drops a parked ref with no `Batches` row; a corrupt row used the infallible decode and aborted the node on the same defensive path - addresses the PR #123 (feature/txpool-ordering-and-proposer-hardening) review: infallible `decode` in `reconstruct_parked`
Summary
drain_epochcan no longer rewind the epoch on a crash-replay of an already-passed output; the boot history walk opts out of the long-read cap and fails loud instead of silently booting with empty watermarks; parked batches persist by digest (ParkedRef) with the snapshot taken under the lock and written outside it, and the authority map becomes aBTreeMapfor deterministic persisted bytes. Legacy by-value blobs still decode via a fallback.self.roundand dropped the evicted headers' digests, permanently gapping the per-authority seq stream on peers; the horizon now keys on the monotonic committed round and eviction requeues FIFO-ahead. The foreign-commit fallback retransmitted every proposed header; it now splits at a grace horizon below the commit round so ordinary lag never triggers it.NodeBatchesCachewrites (the orphan rescue was their only reader) and the boundary flag that justified them; close three robustness gaps (a clean external shutdown misclassified as a crash insiderun_epoch, a multi-worker in-flight wipe fenced byensure!, adebug_assertpromoted to a release assert); restore the observability trio (ForceDrainedtracker stage,AlreadyImporteddemoted to debug,txpoolRPC module enabled).Stack 4/9 of the txpool in-flight tracker and observer-forwarder series.
Surface areas touched
eth_*,rayls_*, faucet)rayls-contracts/)etc/, scripts, Docker, compose).github/workflows/,Makefile)doc/, in-crate READMEs, root docs)Breaking / compatibility
None on the wire. The persisted batch-ordering blob changes shape; a legacy by-value blob is still decoded (pinned by
stored_and_legacy_ordering_blobs_are_distinguishable_by_decode).NodeBatchesCachestays declared (stable column-family set) and is still cleared on foreign-DB sanitization. Thetxpool_*RPC module becomes available.Test plan
drain_epoch_never_rewinds_to_an_already_passed_epoch,stored_and_legacy_ordering_blobs_are_distinguishable_by_decode.fallback_requeue_forgives_ordinary_commit_lagfails at grace 0,digests_survive_gc_advance_and_later_round_commitfails when the requeue drops;push_digest_never_dropsandfallback_requeue_stops_at_the_commit_roundpin the contracts.NodeBatchesCacherow is written.run_epochshutdown race and the multi-worker wipe need multi-task fault injection to demonstrate red and land as hardening without failing-first tests.make checkon the stack tip; CI on this branch.