feat(relayer): persist eth scan progress#344
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a checkpointing mechanism to track and persist scan progress during Ethereum deposit event polling. It updates WatchDepositEvents to accept an onProgress callback, which emits in-order checkpoint events that the processor handles by persisting the offset without triggering transfer processing. A critical logic error was identified in pkg/ethereum/client.go where currentBlock is updated before onProgress successfully executes. If onProgress fails, the poller would skip re-scanning the failed block range on the next tick. It is recommended to only update the block tracking state after onProgress successfully completes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #344 +/- ##
=======================================
Coverage ? 31.75%
=======================================
Files ? 154
Lines ? 11987
Branches ? 0
=======================================
Hits ? 3806
Misses ? 7888
Partials ? 293
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
68a4954 to
935e509
Compare
The relayer only persisted its Ethereum scan offset as a side effect of
processing a deposit, so block ranges with no deposits never advanced the
persisted offset — on restart the poller re-scanned from the last block that
happened to contain a deposit.
The poller now emits a scan-progress checkpoint after every fully scanned
slice (including empty ones), carried on the existing handler as a
DepositEvent{Checkpoint: true}. The Ethereum source turns it into a checkpoint
relayer.Event that rides the same in-order channel behind that slice's
deposits; the processor persists the offset from it and does no transfer work.
Because the checkpoint is delivered in order after the slice's deposits — and
each deposit is durably recorded via CreateTransfer (idempotent) before submit —
the offset never advances past an unprocessed deposit, so nothing is missed or
double-processed. Carrying the signal on the existing handler keeps the
EthereumBridgeClient interface (and its mock) unchanged.
935e509 to
fef4ffa
Compare
Move currentBlock/setLastScannedBlock past the checkpoint handler call so a failed checkpoint re-scans and re-checkpoints the same range on the next tick instead of skipping its watermark, matching the documented intent.
dhyaniarun1993
left a comment
There was a problem hiding this comment.
Yea, I encountered this bug while testing prompt token integration. Thanks for looking into this.
Problem
The relayer only persisted its Ethereum scan offset as a side effect of processing a deposit. Ranges with no deposits advanced the in-memory scan position but never wrote to
chain_state, so on restart the poller resumed from the last block that happened to contain a deposit — re-scanning potentially large empty ranges (or, ifeth_start_blockwas bumped manually to compensate, risking skipped blocks).Fix
The poller now emits a scan-progress checkpoint after every fully scanned slice — including slices with no deposits — and the DB offset is persisted from it in real time.
eth_getLogsslice,WatchDepositEventscalls the existing handler with aDepositEvent{Checkpoint: true}carrying the last scanned block. Progress advances only through the last successful slice; a failing slice and everything after it is retried next tick.Checkpointrelayer.Eventthat rides the same ordered channel behind that slice's deposits.persistOffsetdedupes against the last saved offset.Carrying the signal on the existing handler (rather than a new
onProgresscallback) keeps theEthereumBridgeClientinterface — and its generated mock — unchanged, so the diff stays contained.Nothing missed / nothing double-processed
CreateTransfer(idempotent,ON CONFLICT DO NOTHING) before submission is attempted. A failed submit stayspendingand is retried by the reconcile loop, independent of scan progress — so advancing the scan offset past it can't drop it.offset+1, and any re-seen deposit is deduped byCreateTransfer.Tests
TestProcessor_Start_CheckpointPersistsOffsetWithoutProcessing— a checkpoint event persists the offset and runs no transfer processing.TestEthereumSource_StreamEvents_EmitsScanCheckpoint— a checkpointDepositEventfrom the handler becomes a checkpointrelayer.Eventcarrying the scanned block.