feat(bot): add the hardened Inbox bot mode - #187
Draft
spalladino wants to merge 4 commits into
Draft
spalladino wants to merge 4 commits into
spalladino wants to merge 4 commits into
Conversation
spalladino
added this pull request to stack #188
September 12, 2026 04:52
spalladino
force-pushed
the
spl/fi-n11-inbox-bot
branch
from
September 12, 2026 05:24
2d42880 to
191732a
Compare
Adds the OTel metric definitions and span attributes the Inbox bot mode reports under, plus its three configuration environment variables.
Adds a fourth bot mode that exercises the Fast Inbox end to end: it sends L1->L2 messages in atomic Inbox batches, tracks each message through a durable store, consumes it on L2 through the public or private domain, and checks the node's message APIs against what L1 actually recorded. The bot keeps its own state in a KV store so a restart resumes in-flight messages rather than abandoning them. Batch production reconciles against L1 receipts, message transitions are guarded by their expected state, and an L1 RPC failure is never read as negative evidence: only a viem BlockNotFoundError means a block is no longer canonical and only TransactionReceiptNotFoundError means a batch has no receipt, so an RPC outage leaves batch state untouched instead of failing every in-flight batch as a reorg. An optional saturation schedule periodically sends a full bucket plus the message that rolls it over. The schedule owns its run from the moment the batch is reserved, so a crash during the L1 wait cannot leave an orphan batch that reconciliation adopts and the schedule then follows with a second full-bucket send. The bot reads the L1 Inbox's own bucket layout as an external probe. L1 still batches messages by L1 block, so MessageSent.bucketSeq and Inbox.getBucket remain part of L1's surface even though the node's own store no longer models buckets.
Drives the Inbox bot against a real node and L1, asserting the message lifecycle, the node API checks, the bucket rollover probe and the exported telemetry.
dispatchAttempts starts runConsumptionAttempt without awaiting it, and the observed -> preparing store write is the first await inside that job, so consumeStep can return before the transition has landed. The test asserted the state immediately after consumeStep and lost the race under CI's two-CPU container. It now waits for preparing instead, which still fails if the transition never arrives. The race did not reproduce locally (single-CPU pinning and CPU contention both pass), so there is no local red for it; the ordering is established by reading dispatchAttempts.
spalladino
force-pushed
the
spl/fi-n11-inbox-bot
branch
from
September 15, 2026 22:39
a84c09a to
738f2b5
Compare
spalladino
removed this pull request from stack #188
September 15, 2026 22:42
spalladino
changed the base branch from
spl/fi-n10-docs-corrections
to
spl/a-2041-append-messages-before-txs
September 15, 2026 22:42
spalladino
added this pull request to stack #217
September 15, 2026 22:43
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is the top of the Fast Inbox node stack. It adds a fourth bot mode,
inbox, whose job is tokeep pressure on the Fast Inbox in a live network and to tell us, in metrics rather than in
anecdotes, whether an L1→L2 message that L1 accepted actually became usable on L2 and in how long.
The bot sends messages to the L1 Inbox in atomic batches, records every message it produced in a
durable KV store, waits for the message to become ready on L2, consumes it through either the public
or the private domain, and then checks the node's own message APIs against what L1 recorded: that
the node knows the message at all, that the event it reports matches the one L1 emitted, that the
leaf index agrees, that the readiness witness recomputes to the block header's message-tree root,
that consumption produced the expected nullifier, and that a replay of the same message is refused.
Each of those is a named check with its own counter, so a disagreement shows up as a metric and not
as a stuck bot.
An optional saturation schedule periodically sends a full Inbox bucket plus the one message that
rolls it over, which is the case most likely to expose a boundary bug and the one least likely to
occur by accident under ordinary traffic.
The durable store is what makes the bot restart-safe: in-flight messages and batches survive a
process restart and are reconciled against L1 receipts rather than abandoned, and every milestone is
exported exactly once even if its receipt is resolved again after a restart.
Stack position
This is rung 24 of 25 in the Fast Inbox node stack, stacked on
spl/fi-n10-docs-corrections.What this reconstructs
Old PR #25443, at its current source head
e17b6cd0816524def0b4492d93cc88e234b53d63rather than the older plan snapshot
6a1ce3c1e6. It reconstructs the final behavior of all sevensource changes:
3c037ca5e3— Inbox bot mode configuration, telemetry definitions and lifecycle.58230a749b— durable state, L1 batching and the saturation schedule.5cd3fafd27— public and private consumption, node API checks and the replay probe.50d6672446— OTel metrics, structured logs and spans.6d5d6ff6fb— integration tests, the bucket rollover check and the README.6a1ce3c1e6— the claim-secret call correction (generateClaimSecretis called without a loggeron the batch path, because it logs the secret when handed one).
e17b6cd081— durability, RPC, reorg and state-machine hardening.It lands as three commits, each of which builds on its own: the telemetry and env-var definitions,
the bot package itself, and the single-node e2e suite.
The L1 bucket probe is deliberate
The bot reads the L1 Inbox's bucket layout —
MessageSent.bucketSeqfrom the event andInbox.getBucket(seq, { blockNumber })pinned to the batch's own L1 block — and checks that abatch's messages landed in the buckets the ordering implies, including the rollover when a batch
crosses a boundary.
That is not a leftover of the model the node used to carry. The node's own store is bucketless now
and stays that way; L1 still batches messages by L1 block, so the bucket sequence remains part of
L1's surface. The bot querying it is an external probe of L1's behavior, checked against a node that
does not model buckets at all — which is exactly the disagreement worth detecting. It should stay.
Saturation batch default — flagged, not changed
The ported config defaults
inboxSaturationIntervalSecondsto 86400, so the daily 257-messagesaturation batch is enabled by default. That batch is a real L1 cost. The deployment follow-up plan says
the bot must ship with it disabled (
BOT_INBOX_SATURATION_INTERVAL_SECONDS=0). This PR does notchange the default, because that is a deployment policy decision rather than a porting one; the
deployment values in the deployment follow-up must set it to
0explicitly.Tests actually run
From
yarn-project:yarn build— green. Verified again with the e2e file removed, i.e. at the second commit's tree.yarn format,yarn lint— clean over the whole project.yarn workspace @aztec-labs/bot test— base 3 suites / 19 tests (the pre-existing bot suites),final 13 suites / 173 tests, all passing.
yarn workspace @aztec-labs/telemetry-client test— 5 suites / 39, passing.yarn workspace @aztec-labs/foundation test— 88 suites / 1076 passing, 5 skipped. Thetelemetry-client and foundation changes are purely additive (new exported constants, three new
EnvVarunion members), so these are parity runs rather than new coverage.end-to-end/src/single-node/cross-chain/inbox_bot.test.tsis ported and compiles, but is not runhere. It is a single-node cross-chain suite that stands up a node and L1; earlier rungs in this
stack established that these suites exceed the local tool timeout. It is left to CI.
Red/green
Most of this rung is new code, where the honest evidence is unit coverage of new behavior rather
than a red-then-green transition — the 154 new bot tests are that coverage, not a regression proof.
The seventh source change is different: it hardens behavior that was wrong before, so it gets real
red/green. Reverting the four production files it touches (
inbox_bot.ts,inbox_l1_producer.ts,l1_to_l2_seeding.ts,store/inbox_store.ts) to their pre-hardening state, with the final testsleft in place, produces 18 failures across 4 suites, covering every condition the hardening
addresses:
so a crashed send produces a second full-bucket batch; retention deletes the batch a run is
waiting on; a run whose record is gone holds the schedule forever; a run reports a verdict before
its replay probe resolves.
isL1BlockCanonicalreports a transport failure as areorged block, and
getBatchOutcomereports one as a batch with no receipt.the previous one instead of failing it; a re-mine during the insertion search yields a wrong block
relation instead of an unknown one.
attempt was in flight and export a second outcome for it; a consumption whose effects do not carry
the message nullifier is completed rather than failed; dispatch ignores
maxPendingTxs; the botdoes not exit on
stopWhenUnhealthy; a milestone is exported twice when a receipt is resolvedagain.
Restoring the four files returns the suite to 13/13 and 173/173.
Dependency and lockfile
bot/package.jsongains@aztec-labs/constants(this repo scopes every workspace package@aztec-labs/, so the source's@aztec/constantsmaps to that) forMAX_L1_TO_L2_MSGS_PER_BLOCK,which fixes the bucket size the saturation batch targets, and exports
./testingso the e2e suitecan use the recording telemetry client. Project references were regenerated with the repo's
yarn && yarn prepareworkflow rather than hand-edited. The lockfile change is a single added line(
"@aztec-labs/constants": "workspace:^"under the bot's entry) and nothing else.Deliberately left out
docs/docs-operate/operators/reference/changelog/v6.mdstays inaztec-packages; this repo doesnot own it.
spartan/Helm chart, Terraform, or environment file istouched. Wiring
BOT_MODE=inboxinto the deployment stack is the deployment follow-up, which follows thisstack. (The bot README mentions
spartan/metricsin prose when explaining how OTel units reachPrometheus; no file under
spartan/changes.)MessageWitnessVerdictofunverifiableininbox_message_checks.ts, returned when a leaf indexis too large to hash up with. That is a local merkle-witness verdict and is unrelated to the
deferred proposal-level
unverifiable/checkpoint-unverifiablestatus and sentinel encoding.