Skip to content

Commit e1711d3

Browse files
authored
fix(sequencer): prevent unhandled rejections from in-flight L1 requests during shutdown (v5 line) (#24560)
Ports the remaining part of the `sentinel_status_slash` flake fix to the v5 line. The flake crashed the jest process after all tests passed: an in-flight `eth_getBlockByNumber` against the torn-down anvil surfaced as an unhandled `L1RpcError` (`ECONNREFUSED`). CI log: http://ci.aztec-labs.com/9bb09d2f904e290a This complements #24551, which (after retargeting to `merge-train/spartan-v5`) now carries only the two files that cherry-picked cleanly from `next` — `l1_tx_utils.ts` (guard the loop-top `getL1Timestamp()` in `monitorTransaction`) and `sequencer-publisher.ts` (`.catch` on the fire-and-forget `backupDroppedInSim`). The other two files had diverged on this branch and are addressed here; #24551's title/body should be updated to reflect that it covers 2 of the original 4 files, with this PR covering the rest. ### `yarn-project/ethereum/src/publisher_manager.ts` — fixed This branch's `stop()` (which, unlike `next`, also clears the `started` flag and supports restart via `start()`) interrupted all publishers and the funder but returned immediately, so an in-flight tx monitor loop could still be issuing L1 RPC calls after test teardown. Applied the equivalent of the `next` fix, fitted to this branch's structure: `stop()` now awaits `waitMonitoringStopped()` on every publisher and the funder before returning. `L1TxUtils.waitMonitoringStopped(timeoutSeconds = 10)` already exists on this branch, is self-bounded, and swallows its own timeout with a warning, so shutdown cannot hang. ### `yarn-project/sequencer-client/src/sequencer/checkpoint_proposal_job.ts` — no change needed On `next`, the votes-only path assigned a bare `pendingL1Submission = publisher.sendRequestsAt(...)` with no rejection handler, and only the last job's submission was awaited at shutdown, so an orphaned job's rejection could crash the process. On this branch that field no longer exists: both fire-and-forget paths go through `this.pendingRequests.trackRequest(promise, () => this.interrupt())`, backed by the sequencer's shared `RequestsTracker` (`requests_tracker.ts`). `trackRequest` attaches a rejection handler synchronously at track time (`promise.then(delete, delete)`), so a tracked promise can never surface as an unhandled rejection regardless of whether shutdown awaits it. Additionally, `Sequencer.stop()` calls `pendingRequests.interruptRequests()` followed by `awaitRequests()`, so all tracked requests — not just the last one — are interrupted and drained at shutdown. The `next` bug does not exist here, so no change was forced. (Minor observability note, not a bug: a rejection on the votes-only path is swallowed silently by the tracker rather than logged.) Verified `publisher_manager.ts` parses cleanly; the full workspace type-check runs in CI (this container lacks the bootstrapped bb.js/noir/l1-artifacts toolchain). No tracked issue. --- *Created by [claudebox](https://claudebox.work/v2/sessions/0959caa9f03f59fa) · group: `slackbot`*
1 parent d4d77e3 commit e1711d3

2 files changed

Lines changed: 13 additions & 2 deletions

File tree

yarn-project/ethereum/src/publisher_manager.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,4 +565,8 @@ class TestL1TxUtils {
565565
public restart() {
566566
this.interrupted = false;
567567
}
568+
569+
public waitMonitoringStopped(_timeoutSeconds = 10) {
570+
return Promise.resolve();
571+
}
568572
}

yarn-project/ethereum/src/publisher_manager.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,21 @@ export class PublisherManager<UtilsType extends L1TxUtils = L1TxUtils> {
107107
}
108108

109109
/**
110-
* Stops the funding loop and interrupts all publishers so no further L1 txs are sent. Idempotent, and
111-
* the manager may be restarted afterwards via {@link start}, which clears the interrupted flag.
110+
* Stops the funding loop, interrupts all publishers so no further L1 txs are sent, and waits (bounded)
111+
* for their in-flight tx monitor loops to wind down. Idempotent, and the manager may be restarted
112+
* afterwards via {@link start}, which clears the interrupted flag.
112113
*/
113114
public async stop(): Promise<void> {
114115
this.started = false;
115116
await this.fundingPromise?.stop();
116117
this.publishers.forEach(pub => pub.interrupt());
117118
this.funder?.interrupt();
119+
// Wait for in-flight tx monitor loops to observe the interrupt, so no L1 requests are still
120+
// being issued after shutdown (e.g. against an anvil instance a test is about to tear down).
121+
await Promise.all([
122+
...this.publishers.map(pub => pub.waitMonitoringStopped()),
123+
this.funder?.waitMonitoringStopped(),
124+
]);
118125
}
119126

120127
// Finds and prioritises available publishers based on

0 commit comments

Comments
 (0)