Skip to content

test(e2e): webhook coverage for reorgs, mempool, and heartbeats - #511

Open
bkeroack wants to merge 1 commit into
masterfrom
test/webhook-e2e-coverage
Open

test(e2e): webhook coverage for reorgs, mempool, and heartbeats#511
bkeroack wants to merge 1 commit into
masterfrom
test/webhook-e2e-coverage

Conversation

@bkeroack

@bkeroack bkeroack commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

The webhook E2E suite drove only status and chain, and within chain only forward block connection. Three categories of dispatcher behavior had never been exercised over a socket. This adds one test each.

Not stacked — branches off master, no dependents.

What was missing

Reorgs. Reorg notification is why the alert dispatcher exists (it absorbed reorgwebhook=), and it is the one alert an operator cannot reconstruct after the fact: by the time anything polls, the abandoned blocks are off the active chain. a_reorg_delivers_the_disconnects_and_a_deep_reorg_edge invalidates a block two deep and asserts the fork-point marker (from_height 3 → to_height 1), one block_disconnected per rolled-back block, and the one-shot deep_reorg status edge carrying the true depth — all signed, all to a single hook subscribing both categories.

Mempool. No test had ever delivered a mempool event. a_mempool_hook_sees_admission_and_confirmation_and_nothing_else covers enter and leave_confirmed for a real spend, and asserts the negative that matters more: a mempool-only hook received none of the 102 blocks connected during the test. The category mask is what stands between an operator's pager and per-transaction volume, and it had only ever been asserted against an in-process publisher.

Heartbeats. A dead-man's switch that silently never fires is indistinguishable from a healthy node until it is needed. heartbeats_reach_a_hook_downsampled_to_its_interval asserts pings keep arriving and that they are downsampled — on the spacing of consecutive signing timestamps, not on a delivery count. The filter compares whole seconds (as_secs() >= interval), so at a 1s setting the 1 Hz bus loses roughly every other beat to truncation; a count-based bound admits both 1s and 2s and would have passed regardless of configuration. My first draft had exactly that bug.

Negative verification

Each assertion was shown failing before being trusted:

Test Perturbation Observed
reorg --alertreorgdepth=0 marker + both disconnects still arrive; deep_reorg never does
mempool hook also subscribes chain a mempool-only hook received 102 off-category deliveries
heartbeat heartbeat_interval_secs = 1 consecutive deliveries 1s apart

Also

  • broadcast_spend in streaming.rs becomes pub(crate) so the mempool test reuses the canonical block-1-coinbase spend.
  • write_alertfile_keys / node_with_hook_keys let a test set per-hook TOML keys (heartbeat_interval_secs); the existing write_alertfile delegates and its output is byte-identical.
  • quiet_detectors() silences the self-scheduled detectors, so a test asserting on which events arrived isn't interrupted by disk_low raising at startup on a host under 10 GiB free.

Tests only — no production code, no user-facing change, so no changelog entry.

Verification

  • cargo clippy --all-targets --all-features --locked -- -D warnings — clean
  • cargo test --all --locked — pass
  • cargo test --test e2e --locked --features e2e118 passed; 0 failed (115 before)

🤖 Generated with Claude Code

https://claude.ai/code/session_01GJqZbJtTUo7K7G9wJcrvHv

The webhook E2E suite drove only `status` and `chain`, and within `chain`
only forward block connection. Three categories of the dispatcher's
behavior had never been exercised over a socket:

- **Reorgs.** Reorg notification is why the alert dispatcher exists (it
  absorbed `reorgwebhook=`), and it is the one alert an operator cannot
  reconstruct after the fact: by the time anything polls, the abandoned
  blocks are off the active chain. The new test invalidates a block two
  deep and asserts the fork-point marker, one disconnect per rolled-back
  block, and the one-shot `deep_reorg` status edge with the true depth --
  all signed, all to one hook subscribing both categories.

- **Mempool.** No test had ever delivered a `mempool` event. The new one
  covers admission and confirmation of a real spend, and asserts the
  negative that matters more: a mempool-only hook received none of the
  102 blocks connected during the test. The category mask is what stands
  between an operator's pager and per-transaction volume, and it was only
  ever asserted against an in-process publisher.

- **Heartbeats.** A dead-man's switch that silently never fires is
  indistinguishable from a healthy node until it is needed. Downsampling
  is asserted on the spacing of consecutive signing timestamps rather
  than on a delivery count: the filter compares whole seconds, so at a 1s
  setting the 1 Hz bus loses roughly every other beat to truncation and
  any count-based bound admits both 1s and 2s.

Every assertion was negative-verified by disabling the behavior it
covers and observing the specific failure: `alertreorgdepth=0` leaves the
marker and disconnects arriving but no `deep_reorg`; adding `chain` to
the mempool hook reports 102 off-category deliveries; a 1s heartbeat
interval reports consecutive deliveries 1s apart.

`broadcast_spend` becomes `pub(crate)` so the mempool test reuses the
canonical block-1-coinbase spend rather than open-coding one.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GJqZbJtTUo7K7G9wJcrvHv
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant