Skip to content

Monitoring: fail closed on undelivered events - #961

Merged
piotr-roslaniec merged 7 commits into
devfrom
codex/monitoring-delivery-hardening
Aug 28, 2026
Merged

Monitoring: fail closed on undelivered events#961
piotr-roslaniec merged 7 commits into
devfrom
codex/monitoring-delivery-hardening

Conversation

@mswilkison

@mswilkison mswilkison commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Treat missing Sentry event IDs and failed flushes as receiver failures
  • Allow duplicate acknowledgements while preventing unhandled system events from checkpointing silently
  • Cap trigger block ranges and checkpoint only the processed range
  • Bound persisted handled-event history per receiver

Validation

  • CI passes: monitoring format and monitoring build
  • git diff --check passed locally for the safe-fix batch

Notes

This is public-safe operational hardening only and does not include any private deployment mitigation.

Summary by CodeRabbit

  • Reliability

    • Monitoring catch-up runs are capped at 10,000 blocks to improve stability during large backlogs.
    • Sentry delivery failures now fail safely and are clearly reported.
    • Duplicate events are recognized and prevented from being redelivered.
    • Event history is limited to the 5,000 most recent events per receiver.
  • Documentation

    • Added operator guidance for delivery and dispatch failures.
  • Tests

    • Added automated coverage for event deduplication, dispatch failures, and Sentry delivery outcomes.

@mswilkison
mswilkison force-pushed the codex/monitoring-delivery-hardening branch 2 times, most recently from 47b41c0 to 5b0b0ad Compare May 5, 2026 15:46
@mswilkison
mswilkison marked this pull request as ready for review May 5, 2026 16:45
@piotr-roslaniec
piotr-roslaniec enabled auto-merge (squash) August 5, 2026 12:08
mswilkison and others added 4 commits August 5, 2026 14:08
…errors

- Coverage check now skips events where all receivers returned "ignored"
  (no receiver configured for that event type), allowing deployments that
  run with only a subset of receivers without halting monitoring.
- Coverage check also skips events whose dispatches were rejected, since
  the dispatch error is already recorded and a second coverage error would
  be misleading.
- Replace switch/case dispatch blocks with if/else for clarity.
- Replace async-reduce test runner with a for-of loop for readability.
- Document the 10,000-block catch-up cap and the new dispatch coverage
  failure mode in README.adoc.
@piotr-roslaniec
piotr-roslaniec force-pushed the codex/monitoring-delivery-hardening branch from 751e35a to f354669 Compare August 5, 2026 12:08
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR tightens monitoring execution and delivery rules, limits scan and retention ranges, adds duplicate acknowledgement handling, updates the monitoring README, and adds a compiled test runner with CI execution.

Changes

Monitoring reliability updates

Layer / File(s) Summary
Test runner and CI setup
monitoring/test/*, monitoring/tsconfig.test.json, monitoring/package.json, .github/workflows/monitoring.yml, .gitignore, monitoring/.eslintignore, monitoring/.prettierignore
Adds a local test runner, a test TypeScript build, a yarn test script, CI execution of that script, and ignore rules for dist-test/.
Bounded catch-up and persistence trimming
monitoring/src/system-event.ts, monitoring/src/file-persistence.ts, monitoring/README.adoc
Caps each catch-up pass at 10,000 blocks, advances checkpoints only through the scanned range, trims stored handled events to 5,000 per receiver, and documents the bounded scan behavior.
Fail-closed Sentry delivery
monitoring/src/sentry-receiver.ts, monitoring/test/sentry-receiver.test.ts, monitoring/README.adoc
Requires Sentry.captureMessage to return an event ID and Sentry.flush(2000) to succeed. Tests cover both failure cases, and the README documents the delivery behavior and troubleshooting.
Dispatch acknowledgement and duplicate handling
monitoring/src/system-event.ts, monitoring/test/system-event-manager.test.ts
Adds the "duplicate" acknowledgement status, updates dispatch result handling to track fulfilled and rejected outcomes, treats handled and duplicate acknowledgements as dispatched, and tests ignored, failed, and duplicate delivery cases.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: monitoring now fails closed when events are not delivered. It is concise and specific.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/monitoring-delivery-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@monitoring/src/system-event.ts`:
- Around line 280-283: Update the acknowledgement-status guard in trigger so an
empty this.receivers collection reports a coverage failure instead of returning
and advancing the checkpoint; only skip coverage when at least one receiver was
attempted and every dispatch was rejected. Add a test covering trigger with no
registered receivers and verify the monitor event is not silently skipped.
- Line 165: Adjust the toBlock calculation in the system event scan so the
inclusive queryFilter range covers at most maxBlockRange blocks by subtracting
one from the upper-bound offset. Add coverage verifying the boundary case scans
exactly 10,000 blocks and preserves the latestBlock cap.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7b0b9c7b-c183-45c9-801d-cc995ca3aec0

📥 Commits

Reviewing files that changed from the base of the PR and between 443d6df and f354669.

📒 Files selected for processing (14)
  • .github/workflows/monitoring.yml
  • .gitignore
  • monitoring/.eslintignore
  • monitoring/.prettierignore
  • monitoring/README.adoc
  • monitoring/package.json
  • monitoring/src/file-persistence.ts
  • monitoring/src/sentry-receiver.ts
  • monitoring/src/system-event.ts
  • monitoring/test/run.ts
  • monitoring/test/sentry-receiver.test.ts
  • monitoring/test/system-event-manager.test.ts
  • monitoring/test/test-runner.ts
  • monitoring/tsconfig.test.json

fromBlock - reorgDepthBlocks > 0 ? fromBlock - reorgDepthBlocks : 0

const toBlock = latestBlock
const toBlock = Math.min(latestBlock, fromBlock + maxBlockRange)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate file =="
git ls-files | rg '(^|/)monitoring/src/system-event\.ts$|system-event' || true

echo "== file outline =="
ast-grep outline monitoring/src/system-event.ts || true

echo "== relevant lines =="
sed -n '130,205p' monitoring/src/system-event.ts

echo "== occurrences of maxBlockRange / toBlock / range bounds =="
rg -n "maxBlockRange|toBlock|fromBlock|blockRange|inclusive|range" monitoring/src/system-event.ts monitoring/src || true

echo "== nearby tests/usages =="
rg -n "system-event|monitor block|block range|maxBlockRange|fromBlock|toBlock" monitoring -S || true

Repository: threshold-network/tbtc-v2

Length of output: 18601


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deposit-monitor check implementation =="
sed -n '55,80p' monitoring/src/deposit-monitor.ts

echo "== minting / redemption / wallet / supply implementations around range calls =="
for f in monitoring/src/minting-monitor.ts monitoring/src/redemption-monitor.ts monitoring/src/wallet-monitor.ts monitoring/src/supply-monitor.ts; do
  echo "--- $f ---"
  rg -n "for .*block|blockRange|range.|toBlock|fromBlock|maxRange|loadChainDataCache|query.*block|block.*filter|filter.*block|includes|fromBlock <=" "$f" -C 2 || rg -n "block" "$f" -C 2 || true
done

echo "== Manager loop around check range =="
sed -n '208,235p' monitoring/src/system-event.ts

echo "== tests for system-event manager triggers =="
sed -n '1,220p' monitoring/test/system-event-manager.test.ts

echo "== all README max block range mentions =="
rg -n "maximum block range|10,000|10000|max block range|maxBlockRange|scan" monitoring/README.adoc -C 2

Repository: threshold-network/tbtc-v2

Length of output: 12495


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== find event query helpers =="
rg -n "getDepositRevealedEvents|getRedemptionRequestedEvents|getNewWalletRegisteredEvents|getDkgResultSubmittedEvents|getDkgResultApprovedEvents|getDkgResultChallengedEvents|getOptimisticMintingRequestedEvents|withTopics|events|queryBlocks|eth_fetch|toBlock|fromBlock" . \
  -g '*.ts' -g '*.json' -g '*.js' \
  -C 3 || true

echo "== package / dependency clues =="
for f in package.json monitoring/package.json package-lock.json pnpm-lock.yaml yarn.lock; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,180p' "$f"
  fi
done

Repository: threshold-network/tbtc-v2

Length of output: 50382


🌐 Web query:

ethers.js QueryFilter fromBlock toBlock inclusive range

💡 Result:

In ethers.js, the fromBlock and toBlock parameters used in queryFilter and general Filter objects define an inclusive range for searching blockchain logs [1][2][3]. When using the contract.queryFilter(event, fromBlock, toBlock) method: - fromBlock: Specifies the starting block of the search range. It is inclusive, meaning logs from this block are included [1]. It defaults to 0 if not provided [1][4]. - toBlock: Specifies the ending block of the search range. It is also inclusive, meaning logs up to and including this block are retrieved [1]. It defaults to "latest" if not provided [1][4]. Because both boundaries are inclusive, specifying the same block number for both fromBlock and toBlock will restrict the search to that specific block. These properties are part of the broader Filter interface in ethers.js, which applies to both high-level contract queries and low-level provider.getLogs calls [2][3].

Citations:


Cap the inclusive scan to 10,000 blocks.

toBlock passes an inclusive ethers.js queryFilter range, so fromBlock + 10000 scans 10,001 blocks. Use fromBlock + maxBlockRange - 1, or define maxBlockRange as exclusive. Add coverage for the boundary case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monitoring/src/system-event.ts` at line 165, Adjust the toBlock calculation
in the system event scan so the inclusive queryFilter range covers at most
maxBlockRange blocks by subtracting one from the upper-bound offset. Add
coverage verifying the boundary case scans exactly 10,000 blocks and preserves
the latestBlock cap.

Comment on lines +280 to +283
statuses.size === 0 ||
(statuses.size === 1 && statuses.has("ignored"))
)
return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Report a coverage failure when no receiver is registered.

Line 280 treats zero acknowledgements as rejected dispatches. With this.receivers empty, no dispatch rejects, this branch returns, and trigger advances the checkpoint. The monitor events are then skipped without delivery. Only skip coverage when at least one receiver was attempted and all dispatches rejected. Add a test with an empty receiver list.

Proposed fix
-      if (
-        statuses.size === 0 ||
-        (statuses.size === 1 && statuses.has("ignored"))
-      )
+      const allDispatchesRejected =
+        this.receivers.length > 0 && statuses.size === 0
+      const allReceiversIgnored =
+        statuses.size === 1 && statuses.has("ignored")
+      if (allDispatchesRejected || allReceiversIgnored)
         return
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
statuses.size === 0 ||
(statuses.size === 1 && statuses.has("ignored"))
)
return
const allDispatchesRejected =
this.receivers.length > 0 && statuses.size === 0
const allReceiversIgnored =
statuses.size === 1 && statuses.has("ignored")
if (allDispatchesRejected || allReceiversIgnored)
return
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@monitoring/src/system-event.ts` around lines 280 - 283, Update the
acknowledgement-status guard in trigger so an empty this.receivers collection
reports a coverage failure instead of returning and advancing the checkpoint;
only skip coverage when at least one receiver was attempted and every dispatch
was rejected. Add a test covering trigger with no registered receivers and
verify the monitor event is not silently skipped.

@piotr-roslaniec
piotr-roslaniec changed the base branch from main to dev August 26, 2026 15:07
@piotr-roslaniec
piotr-roslaniec merged commit 9329303 into dev Aug 28, 2026
44 checks passed
@piotr-roslaniec
piotr-roslaniec deleted the codex/monitoring-delivery-hardening branch August 28, 2026 09:11
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.

2 participants