Skip to content

fix(replay): persist message drops - #1447

Merged
tt-cll merged 14 commits into
mainfrom
tt/replayux
Oct 2, 2026
Merged

tt-cll merged 14 commits into
mainfrom
tt/replayux

Conversation

@tt-cll

@tt-cll tt-cll commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

Description

Addresses CCIP-13501 (durable drop evidence) and CCIP-13502 (live source-range recovery) for the standalone verifier. Stacks on the archive inventory / CLI work (CCIP-13475/13499/13500), which shipped without a schema change; the durable storage those tickets did not need arrives here.

CCIP-13501 — durable drop & finality-incident evidence (ccv_recovery_events, verifier/pkg/recovery)

  • Confirmed curse and disablement drops, and finality incidents that flush the pending queue, are persisted as recovery events with owner/node, message ID, chains, source block, tx/block hashes, stage, bounded reason, and incident references — operators no longer depend on retained logs/traces to find message IDs and source blocks
  • Finality incidents record the detection block, available conflicting hashes, and pending/sent flush counts, and are committed transactionally with their known pending messages; no synthetic failed verification jobs are created
  • ccv recovery events queries by message IDs, owner, source/dest chain, reason, observation window, and source-block range, paginated (before-id cursor) as machine-readable JSON with coverage metadata — an empty page does not imply there was no incident or no affected traffic
  • Dedup by owner/node/chain/message/block/tx/reason/incident; re-observation updates last_observed_at and extends retention; history survives restart with explicit retention cleanup
  • An audit-write failure is surfaced via metrics/logs and never unblocks a finality violation; ordinary pending-finality waits and unknown curse/rule states are never labeled permanent drops; messages never observed during a disabled interval remain an explicit scoping limitation (replay canonical source events to find them)

CCIP-13502 — live bounded source-range recovery (ccv_recovery_operations, ccv_recovery_readers, verifier/pkg/sourcereader/recovery.go)

  • ccv recovery replay/list/status/cancel/resume on the standalone CLI: submit an explicit verifier owner, source chain, and inclusive block range while the process keeps running; an omitted --to-block is captured as a fixed target at submission (never a moving head); returns a durable operation ID; --request-id is an idempotency key
  • The running reader coordinates replay on its event loop: separate replay progress so normal polling/checkpoint batching cannot overwrite recovery progress or skip blocks; a CLI/browser disconnect loses nothing
  • Canonical source events are re-read through the same finality-readiness, curse, and disablement admission path (single admission() shared with live polling); admitted work follows normal verification and policy; existing active jobs ride the queue's uniqueness rules; replay does not revoke earlier attestations or reconcile old archived rows
  • An explicit, investigated reset operation reinitializes and re-enables a finality-blocked reader in process (including readers excluded at startup), recording the chosen boundary and operator action; a new violation blocks again — checker resets do not reconstruct prior in-memory hash history
  • Bounded chunk sizes, concurrency, and queue capacity so recovery does not starve normal traffic; progress via operation status and bounded metrics, operation IDs in records/logs (not metric labels); cancellation stops further scans without pretending to retract published jobs; interruption resumes from persisted progress
  • Stop/change/start remains the documented fallback for older versions; a live operation never silently restarts the service

One migration (00009_source_recovery.sql) adds the three tables. Also merges origin/main and reconciles main's span-per-message tracing refactor with this branch's admission/recovery logic in sourcereader/service.go.

Standalone verifier only. The Chainlink core-node deployment intentionally applies neither the migrations nor the ccv recovery command group; supporting CL mode is out of scope — it adds deployment complexity (conditional migration application, core command wiring, separate schema lifecycles) for no current operator need. Core wiring must stay conditional (see the changelog's Compatibility section); extending this to CL mode, if ever, is a separate effort.

Testing

  • Unit: verifier/pkg/recovery (store dedup/retention, operations state machine, metrics), verifier/pkg/sourcereader (admission, recovery chunks, all three drop causes, restart), cli/recovery / cli/jobqueue (commands, postgres store), finality checker
  • Devenv e2e: recovery CLI coverage (recovery of a missed range and pre-admission drops with no verifier restart, finality-incident reset, cancellation/resume, existing active job, concurrent normal traffic)
  • Unavailable audit storage: audit-write failure is observable and the finality block holds
  • Post-merge: go test ./verifier/pkg/sourcereader/... passes; conflicts resolved in sourcereader/service.go, cli/jobqueue/README.md, verifiercli e2e client

Checklist

  • Breaking changes documented in changelog (see changelog directory)
  • Cross link related PRs (in this or other repositories)

Base automatically changed from tt/recoveryux to main September 16, 2026 13:15
@tt-cll
tt-cll marked this pull request as ready for review September 21, 2026 21:15
@tt-cll
tt-cll requested review from a team as code owners September 21, 2026 21:15
Copilot AI lite review requested due to automatic review settings September 21, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Critical CL-mode processing and replay-duplication issues, along with other review findings, remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 High severity · 4 Low severity

Open (7)
What changed in this PR

Adds durable recovery evidence and live bounded replay/reset operations for the standalone verifier.

Changes:

  • Adds recovery persistence, admission auditing, finality handling, and retention.
  • Adds resumable recovery CLI workflows and reader/queue coordination.
  • Adds observability, dashboards, documentation, migrations, and E2E coverage.
File Summary
verifier/​pkg/​vtypes/​types.go Adds source block hash metadata.
verifier/​pkg/​sourcereader/​service.go Integrates recovery and admission handling.
verifier/​pkg/​sourcereader/​recovery.go Implements replay and reader reset workflows. Review: critical (1 vote) replay drops do not enter terminal tracking, allowing duplicate rediscovery; moderate (1 vote) reset audit failures are not surfaced; moderate (1 vote) capacity counts failed/completed rows; moderate (1 vote) registration failures use audit-failure telemetry; nit (2 votes) comment exceeds the three-line limit.
verifier/​pkg/​sourcereader/​recovery_test.go Tests recovery behavior and resets.
verifier/​pkg/​sourcereader/​recovery_audit.go Persists drops and finality incidents.
verifier/​pkg/​sourcereader/​finality_checker.go Captures finality evidence.
verifier/​pkg/​sourcereader/​finality_checker_test.go Tests finality evidence.
verifier/​pkg/​sourcereader/​admission.go Shares admission logic.
verifier/​pkg/​recovery/​types.go Defines recovery records and requests.
verifier/​pkg/​recovery/​store.go Implements event persistence and cleanup.
verifier/​pkg/​recovery/​store_test.go Tests event and transaction storage.
verifier/​pkg/​recovery/​operations.go Implements durable operation state management.
verifier/​pkg/​recovery/​metrics.go Adds recovery metrics.
verifier/​pkg/​jobqueue/​postgres_queue.go Initializes archive metrics.
verifier/​pkg/​jobqueue/​observability_decorator.go Collects archive metrics periodically.
verifier/​pkg/​jobqueue/​archive.go Implements archive inventory classification. Review: moderate (1 vote) read-time classification can cause full archive scans and stale inventory; persist/index the bounded category or add matching indexes. Nit (2 votes): shorten the long code comment.
verifier/​pkg/​jobqueue/​archive_test.go Tests archive inventory behavior.
verifier/​pkg/​helpers_test.go Updates configured-reader test helpers.
verifier/​pkg/​coordinator.go Wires recovery into source readers. Review: critical (1 vote) recovery is configured for Chainlink-node integration despite absent migration support, which can stop normal polling; gate it behind standalone schema support.
verifier/​pkg/​chainstatus/​batcher.go Coordinates durable reader resets.
verifier/​pkg/​chainstatus/​batcher_test.go Tests reset coordination.
verifier/​migrations/​postgres/​00009_source_recovery.sql Adds recovery tables.
protocol/​common_types.go Adds optional event block hashes.
integration/​pkg/​accessors/​evm/​evm_source_reader.go Supplies EVM block hashes.
docs/​runbooks/​remediating-stuck-or-dropped-messages.md Documents recovery workflows.
docs/​monitoring/​verifier-recovery.md Documents recovery monitoring.
docs/​monitoring/​verifier-recovery-alerts.yaml Adds recovery alerting.
docs/​monitoring/​verifier-archive-inventory.md Documents archive inventory. Review: nit (2 votes) documentation incorrectly claims migration 00009 adds failure_category and covering indexes.
docs/​monitoring/​verifier-archive-inventory-alerts.yaml Adds archive alerts.
cmd/​verifier/​run_ccv_cli.go Registers recovery commands. Review: nit (1 vote) missing database URL produces an unhelpful <nil> error instead of actionable guidance.
cli/​recovery/​README.md Documents recovery commands.
cli/​recovery/​commands.go Implements recovery CLI commands.
cli/​recovery/​commands_test.go Tests CLI validation and output.
cli/​jobqueue/​README.md Updates archive recovery guidance. Review: nit (2 votes) documents failure_category as persisted/exposed although it is only a query-time expression.
cli/​chainstatuses/​README.md Documents live recovery and fallback behavior.
changelog/​2026-09-11_source_recovery.md Describes recovery changes and rollout. Review: nit (1 vote) inaccurately claims migration 00009 persists failure_category and archive indexes; nit (1 vote) references nonexistent 00009_recovery.sql and incorrect archive schema changes.
changelog/​2026-09-10_archive_inventory_and_cli.md Documents archive inventory context.
build/​devenv/​tests/​e2e/​verifiercli/​recovery.go Adds E2E recovery client helpers.
build/​devenv/​tests/​e2e/​verifiercli/​client.go Adds process identity checks.
build/​devenv/​tests/​e2e/​smoke_recovery_cli_test.go Adds recovery and archive E2E tests.
build/​devenv/​tests/​e2e/​smoke_policy_hook_test.go Uses live recovery for policy tests. Review: critical (1 vote) failure_category is not populated or emitted, so the assertion fails for every policy rejection.
build/​devenv/​tests/​e2e/​smoke_chain_statuses_cli_test.go Tests live disabled-reader reset.
build/​devenv/​tests/​e2e/​smoke_aggregator_message_disablement_rules_test.go Tests recovery after disablement drops.
build/​devenv/​tests/​e2e/​recovery_helpers_test.go Provides shared recovery helpers.
build/​devenv/​tests/​e2e/​finality_reorg_curse_test.go Tests finality and curse recovery scenarios.
build/​devenv/​go.sum Updates dependency checksums.
build/​devenv/​dashboards/​verifier_recovery.json Adds recovery dashboard. Review: moderate (1 vote) uses the archive-inventory metric for the verifier selector instead of verifier_recovery_collection_success.
build/​devenv/​dashboards/​verifier_archive_inventory.json Adds archive inventory dashboard.
.github/​workflows/​test-smoke.yaml Runs recovery smoke tests.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread build/devenv/tests/e2e/smoke_policy_hook_test.go
Comment thread verifier/pkg/coordinator.go Outdated
Comment thread verifier/pkg/sourcereader/recovery.go Outdated
Comment thread cli/jobqueue/README.md Outdated
Comment thread docs/monitoring/verifier-archive-inventory.md Outdated
Comment thread verifier/pkg/jobqueue/archive.go Outdated
Comment thread verifier/pkg/sourcereader/recovery.go Outdated
@tt-cll
tt-cll enabled auto-merge September 22, 2026 22:22
@github-actions

Copy link
Copy Markdown

Code coverage report:

Package main tt/replayux Diff
github.com/smartcontractkit/chainlink-ccv/aggregator 47.86% 47.86% +0.00%
github.com/smartcontractkit/chainlink-ccv/bootstrap 62.47% 62.47% +0.00%
github.com/smartcontractkit/chainlink-ccv/cli 50.63% 53.54% +2.91%
github.com/smartcontractkit/chainlink-ccv/cmd 37.80% 37.80% +0.00%
github.com/smartcontractkit/chainlink-ccv/common 53.60% 53.60% +0.00%
github.com/smartcontractkit/chainlink-ccv/executor 42.14% 42.14% +0.00%
github.com/smartcontractkit/chainlink-ccv/indexer 35.43% 35.38% -0.05%
github.com/smartcontractkit/chainlink-ccv/integration 60.80% 60.80% +0.00%
github.com/smartcontractkit/chainlink-ccv/internal 0.00% 0.00% +0.00%
github.com/smartcontractkit/chainlink-ccv/migration 78.70% 78.70% +0.00%
github.com/smartcontractkit/chainlink-ccv/pkg 81.82% 81.82% +0.00%
github.com/smartcontractkit/chainlink-ccv/pricer 0.00% 0.00% +0.00%
github.com/smartcontractkit/chainlink-ccv/protocol 67.04% 67.04% +0.00%
github.com/smartcontractkit/chainlink-ccv/tools 39.19% 39.19% +0.00%
github.com/smartcontractkit/chainlink-ccv/verifier 36.11% 34.55% -1.56%
Total 51.40% 50.10% -1.30%

Files added (in tt/replayux):

  • github.com/smartcontractkit/chainlink-ccv/cli/recovery/commands.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/jobqueue/archivecategory/category.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/recovery/operations.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/recovery/store.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/sourcereader/admission.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/sourcereader/recovery.go
  • github.com/smartcontractkit/chainlink-ccv/verifier/pkg/sourcereader/recovery_audit.go

@tt-cll tt-cll mentioned this pull request Sep 25, 2026
2 tasks done
Comment thread protocol/common_types.go
BlockTimestamp time.Time

// BlockHash is optional source evidence supplied by the reader; empty means unavailable.
BlockHash ByteSlice

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

is that possible to make that required? Maybe for new data only? optional field means branching the logic, backfilling if this field is required for some work

@tt-cll
tt-cll added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit 78d7875 Oct 2, 2026
57 checks passed
@tt-cll
tt-cll deleted the tt/replayux branch October 2, 2026 15:58
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.

4 participants