Espresso 3a: Fallback batcher (re-hosted)#458
Conversation
| "(based on L1 tip time) and the verifier's gate (based on the containing " + | ||
| "L1 block's time). Has no effect outside the boundary window around the " + | ||
| "EspressoTime hardfork.", | ||
| Value: 5 * time.Minute, |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/flags/flags.go:172.
Original transcript:
@palango — 2026-06-02:
This is a nice idea, but we need to see how to set this value.
I think we either set this to something big and safe (1-2h) and accept that we're sending a couple of unused tx, or remove it at all and shutdown the batcher before the switch (causing more work for devops).
There was a problem hiding this comment.
@palango technically it's not necessary at all. If the scenario it tries to prevent still happens, the batcher will just detect it here:
optimism/op-batcher/batcher/sync_actions.go
Line 159 in 9b2017e
This will cause at most several minutes of delay before it re-submits, now through the authenticated path. So there shouldn't be any need to shut the batcher down in any case.
| // | ||
| // The contract's fallback path checks msg.sender against systemConfig.batcherHash(), so no | ||
| // separate signature is needed — the L1 transaction is already signed by the TxManager's key. | ||
| func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/batcher/fallback_auth.go:42.
Original transcript:
@palango — 2026-06-02:
Reverted auth tx can be reported as success.
sendTxWithFallbackAuthchecks only err afterl.Txmgr.Sendand never checksverificationReceipt.Statusinop-batcher/batcher/fallback_auth.go:85.txmgr.Sendreturnsreceipt, nilon receipt arrival inop-service/txmgr/txmgr.go:743, and derivation ignores failed auth receipts inop-node/rollup/derive/ batch_authenticator.go:85.
| } | ||
| l.authGroup.Go( | ||
| func() error { | ||
| l.sendTxWithFallbackAuth(txdata, isCancel, candidate, queue, receiptsCh) |
There was a problem hiding this comment.
Mirrored from #448 (unresolved review thread) to keep tracking it here after the re-host. Original location: op-batcher/batcher/espresso_driver.go:67.
Original transcript:
@palango — 2026-06-02:
The normal batch submission path sends every batch tx through
txQueue.Send:
op-batcher/batcher/driver.go:1061That queue is created with
MaxPendingTransactions:
op-batcher/batcher/driver.go:515and
txmgr.Queue.Sendexplicitly assigns nonces synchronously so transactions
confirm in the order they are sent. This is important for Holocene, where
frames for a channel must arrive in order.The fallback-auth path bypasses that queue. Once fallback auth is required,
dispatchAuthenticatedSendTxstarts a goroutine inauthGroup:
op-batcher/batcher/espresso_driver.go:65and that goroutine calls
l.Txmgr.Senddirectly for both the auth tx and the
batch inbox tx:
op-batcher/batcher/fallback_auth.go:85
op-batcher/batcher/fallback_auth.go:95
Txmgr.Sendis concurrency-safe, but it only preserves the order in which
callers actually reach nonce assignment. With up tofallbackAuthGroupLimit = 128goroutines racing, that order is no longer the publishing loop’s frame
order. As a result, batch inbox txs from the same channel can receive nonces
in a different order than the channel manager emitted them, and L1 inclusion
order follows those nonces.That can violate Holocene strict frame ordering and cause derivation to drop
later/non-contiguous frames.This is also a regression from the default config, where
max-pending-tx
defaults to1; operators who configured one-at- a-time submission no longer
get that behavior for fallback-authenticated batches.The fix should preserve the original batch order across the whole auth+inbox
pair. Simply queueing inbox txs after concurrent auth confirmation is not
sufficient, because auth confirmations can complete out of order. The
fallback-auth path should either be serialized, or use an ordered mechanism
that keeps the original frame order while still ensuring each inbox tx is
posted only after its matching auth tx succeeds.
There was a problem hiding this comment.
This was a serious oversight.
Reworked in 6f42f54: queueing is serialized, the goroutine is spawned only to convert the authentication and batch submission receipt pair to a single receipt indicating transaction status. This should both respect the number of in-flight transactions allowed and preserve Holocene ordering.
Added some tests for this behaviour as well.
a587307 to
34c9d0d
Compare
f840eee to
12b46a6
Compare
34c9d0d to
60f92ee
Compare
0f8d63c to
9330de1
Compare
8a45116 to
266fe6f
Compare
9330de1 to
1616378
Compare
Adds the Espresso-introduced contracts and the minimum supporting changes
required for them to compile, test, and pass the contract checks.
New contracts and scripts:
- src/L1/BatchAuthenticator.sol and interfaces/L1/IBatchAuthenticator.sol
(upgradeable contract that authenticates batch transactions, with switching
between Espresso and fallback batchers)
- scripts/deploy/DeployBatchAuthenticator.s.sol and
scripts/deploy/DeployEspresso.s.sol
- test/L1/BatchAuthenticator.t.sol and test/mocks/MockEspressoTEEVerifiers.sol
- snapshots/{abi,storageLayout}/BatchAuthenticator.json
- snapshots/semver-lock.json entry for BatchAuthenticator
New submodules:
- lib/espresso-tee-contracts (interfaces required by BatchAuthenticator)
- lib/openzeppelin-contracts-upgradeable-v5 (OZ v5 used by BatchAuthenticator
via OwnableUpgradeable)
Supporting changes (Espresso-driven):
- foundry.toml: remappings for OZ v5 and espresso-tee-contracts; ignored
warning codes for vendored libs; OOM-safe jobs settings; via-ir profile.
- justfile: fix-proxy-artifact recipe to handle OZ v5 shadowing Proxy/ProxyAdmin
artifacts; build/coverage hooks.
- src/universal/Proxy.sol, src/universal/ProxyAdmin.sol: pin pragma to exact
0.8.15 so they stay in their own compilation group and never emit PUSH0.
- src/universal/ReinitializableBase.sol: loosen pragma to ^0.8.15 so
BatchAuthenticator (compiled with OZ v5) can import it.
- scripts/* and test/*: disambiguate Proxy artifact lookups to
src/universal/Proxy.sol:Proxy (avoids OZ v5 proxy/Proxy.sol shadow).
- scripts/checks: bypass interface checks for artifacts originating from lib/;
add Espresso-related contract names to exclude lists; pragma exclusions for
Proxy/ProxyAdmin/BatchAuthenticator.
- test/vendor/Initializable.t.sol: exclude BatchAuthenticator (deployed by a
separate Espresso script).
Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: piersy <pierspowlesland@gmail.com>
- strict-pragma: remove unneeded exclusions for src/universal/Proxy.sol and src/universal/ProxyAdmin.sol — both already use strict 'pragma solidity 0.8.15;', so the entries (and their misleading comment claiming '^') were dead. - interfaces: move the Espresso excludeContracts block out of the upstream-shared area and down next to the Celo block, with one entry per line to match the surrounding style. Localizes future rebase deltas. Co-authored-by: OpenCode <noreply@opencode.ai>
Inline the EspressoTEEVerifier deployment in DeployEspresso.s.sol so it
no longer imports lib/espresso-tee-contracts/scripts/DeployTEEVerifier.s.sol
or DeployNitroTEEVerifier.s.sol. The upstream scripts pulled OZ v5's
TransparentUpgradeableProxy (and its auto-deployed ProxyAdmin) into the
OP artifact tree, shadowing src/universal/ProxyAdmin.sol and forcing a
~90-line fix-proxy-artifact justfile recipe.
The TEEVerifier is now deployed behind src/universal/Proxy.sol +
src/universal/ProxyAdmin.sol, matching how BatchAuthenticator is
deployed in the same script. ERC-1967 slots are unchanged, so external
callers see no difference.
The raw vm.getCode("ProxyAdmin") lookups in the deploy scripts and
BatchAuthenticator tests are switched to the explicit artifact path
vm.getCode("forge-artifacts/ProxyAdmin.sol/ProxyAdmin.json") to
deterministically resolve the default compilation profile's bytecode
(the dispute profile transitively compiles ProxyAdmin at optimizer_runs=5000,
creating a second artifact that broke unqualified lookups).
The fix-proxy-artifact recipe and its 5 callsites are removed.
Cherry-picked from piersy's commit 5d0a803 on PR #443. Walks the dual-batcher state machine: Espresso path → switchBatcher → fallback path → switchBatcher → Espresso path. Asserts every transition emits the expected event, that signer registration survives the round-trip, and that re-issuing the same call after a mode flip changes the outcome (the previously-valid Espresso signature is no longer consulted on the fallback path). Co-authored-by: Piers Powlesland <pierspowlesland@gmail.com> Co-authored-by: OpenCode <noreply@opencode.ai>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Regenerated against PR #443's BatchAuthenticator.sol via forge build + abigen. Includes the new history-based API (espressoBatcherAt, espressoBatcherAtBlock, espressoBatcherHistoryLength, setEspressoBatcher) and the EspressoBatcherUpdated(address,address,uint64) event with the fromBlock parameter; drops the removed paused() function. Consumed by the fallback batcher (next commit) to read activeIsEspresso and pack authenticateBatchInfo calldata. The TEE batcher in a follow-up PR will use the same binding. Co-authored-by: OpenCode <noreply@opencode.ai>
Add the fallback (non-TEE) batcher's BatchAuthenticator integration: - op-batcher/batcher/fallback_auth.go: sendTxWithFallbackAuth path that posts authenticateBatchInfo before the batch tx, with a deadline check against the batch's L1 inclusion window. Computes the batch commitment hash from either calldata or concatenated blob versioned hashes. - op-batcher/batcher/espresso_active.go: hasBatchAuthenticator (does this rollup use BatchAuthenticator at all?) and isFallbackAuthRequired (gates fallback authentication on Config.IsEspresso(tip.Time + lead)). The Espresso hardfork predicate is consulted with the configured FallbackAuthLeadTime added to the L1 tip, so the batcher starts authenticating slightly before the verifier requires it. This absorbs worst-case L1 inclusion delay between the batcher's decision time (L1 tip) and the verifier's evaluation time (containing L1 block). - op-batcher/batcher/espresso_driver.go: the authGroup bookkeeping (initAuthGroup, waitForAuthGroup, fallbackAuthGroupLimit) and the dispatchAuthenticatedSendTx fan-out used by driver.go sendTx. Small wiring edits to upstream files: - op-batcher/flags/flags.go: register --espresso.fallback-auth-lead-time (default 5m). - op-batcher/batcher/config.go: thread the FallbackAuthLeadTime through CLIConfig. - op-batcher/batcher/service.go: BatcherConfig.FallbackAuthLeadTime field, propagated from CLIConfig in initFromCLIConfig. - op-batcher/batcher/driver.go: extend L1Client to embed bind.ContractBackend (required by the BatchAuthenticator binding), add authGroup field to BatchSubmitter, call initAuthGroup in NewBatchSubmitter, call dispatchAuthenticatedSendTx in sendTx, call waitForAuthGroup in publishingLoop's shutdown drain. - op-batcher/batcher/driver_test.go: embed bind.ContractBackend in fakeL1Client so the AltDA tests still satisfy L1Client. The fallback batcher does nothing when the rollup config has no BatchAuthenticator address, and it falls through to the upstream queue.Send path pre-EspressoTime. Cancel transactions always take the upstream path. No new external dependencies are added; the only third- party Go modules needed are already in PR #445. The TEE batcher is a separate PR stacked on top. Co-authored-by: OpenCode <noreply@opencode.ai>
The Espresso fallback-auth path previously dispatched each auth+batch pair to a separate errgroup and called Txmgr.Send directly, bypassing the operator's MaxPendingTransactions bound and assigning nonces in a nondeterministic order. Under Holocene the frame queue drops out-of-order frames instead of buffering them, so the batcher's L1 txs must land in submission order. Submit the authenticateBatchInfo tx and the batch inbox tx through the same ordered queue.Send path as the non-fallback batcher, in submission order, so the auth tx takes the lower nonce and is mined first, and both txs stay under MaxPendingTransactions. A watcher goroutine (tracked by authGroup so the publishing loop drains it before closing receiptsCh) collects both receipts on private channels, fails the pair if the auth tx reverted (a reverted authenticateBatchInfo emits no event, so the verifier would silently drop the batch), runs the lookback-window check, and emits a single synthetic receipt for the batch txData. Co-authored-by: OpenCode <noreply@opencode.ai>
1616378 to
25a6c63
Compare
266fe6f to
2782976
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49c4321cf1
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // nonces are assigned in submission order. Each Send blocks here when the queue is at its | ||
| // MaxPendingTransactions limit. | ||
| queue.Send(transactionReference, verifyCandidate, authReceiptCh) | ||
| queue.Send(transactionReference, *candidate, batchReceiptCh) |
There was a problem hiding this comment.
Serialize blob batches behind the auth transaction
When blob DA is used with --max-pending-tx set above the default 1 (or 0 for unlimited), this immediately submits a non-blob authenticateBatchInfo tx and then the blob batch tx from the same sender while the auth tx is still pending. The txmgr interface documents that callers mixing Blob and non-Blob transactions must handle ErrAlreadyReserved (op-service/txmgr/txmgr.go:81-83), so this path can repeatedly reject/requeue fallback-auth blob batches instead of posting them. The batch send should wait until the auth tx is mined, or otherwise avoid having both tx types pending from the same account.
Useful? React with 👍 / 👎.
|
|
||
| distance := new(big.Int).Sub(batchResult.Receipt.BlockNumber, authResult.Receipt.BlockNumber) | ||
| lookbackWindow := new(big.Int).SetUint64(derive.BatchAuthLookbackWindow) | ||
| if distance.Sign() < 0 || distance.Cmp(lookbackWindow) >= 0 { |
There was a problem hiding this comment.
It seems to me that op-node loopback window includes differences of [0, loopbackWindow] blocks, while the batcher includes [0, loopbackWindow - 1] (so a block delta of exactly loopbackWindow would be true here). Is this expected?
palango
left a comment
There was a problem hiding this comment.
Inline findings M1–M4 from a multi-persona review of the fallback-auth path. All are MEDIUM (no externally exploitable issue found; the crypto binding is sound). Separately there is one HIGH (H1): the batcher never reads the contract's activeIsEspresso flag, so if the fallback batcher runs while it's still true (the default deploy state) every authenticateBatchInfo reverts → infinite retry → silent safe-head stall + gas bleed. Happy to expand on H1 and the test gaps in the PR thread.
|
|
||
| distance := new(big.Int).Sub(batchResult.Receipt.BlockNumber, authResult.Receipt.BlockNumber) | ||
| lookbackWindow := new(big.Int).SetUint64(derive.BatchAuthLookbackWindow) | ||
| if distance.Sign() < 0 || distance.Cmp(lookbackWindow) >= 0 { |
There was a problem hiding this comment.
M1 — Lookback-window off-by-one (batcher is one block stricter than the verifier).
The verifier accepts an auth event at distance [0, 100] inclusive (101 blocks): CollectAuthenticatedBatches starts currentBlock := ref (distance 0), processes the block, then breaks on ref.Number - currentBlock.Number >= BatchAuthLookbackWindow — so the block at distance 100 is processed before the break (op-node/rollup/derive/batch_authenticator.go:176).
This check rejects distance >= 100, i.e. it re-queues at exactly distance 100 even though the verifier would accept it. The direction is safe (stricter → unnecessary resubmission, never a silent drop), but the two checks should agree. Either use distance.Cmp(lookbackWindow) > 0 to match the verifier, or document the deliberate 1-block safety margin. The op-node/rollup/derive/params.go:24-27 comment is also imprecise about the inclusive 101-block reality.
| authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) | ||
| batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) |
There was a problem hiding this comment.
M2 — max-pending-tx defaults to 1, and this cap-1 buffering is load-bearing.
With MaxPendingTransactions = 1 (the default, op-batcher/flags/flags.go:66), the second queue.Send (line 97) blocks in errgroup.Go until the auth tx mines — so batch submission stalls for a full auth-confirmation latency on every pair, and the batch tx isn't even crafted until the auth tx mines (~doubles per-batch latency).
It avoids deadlock only because these channels are buffered cap 1: the auth handler can write its receipt with no reader present (the watcher goroutine isn't started until after both Sends return, line 99). The comment should state that the buffering is what prevents the deadlock at the queue limit — switching these to unbuffered would hard-deadlock at the default config. Recommend documenting that MaxPendingTransactions >= 2 is required (or auto-bumping it) when fallback auth is active. This serialization is also untested — the fakeTxSender in the unit tests delivers receipts inline and doesn't model the blocking second Send.
There was a problem hiding this comment.
With the changes made in this #467 addressing M3, i believe this would solve this concern as well. Let me know if you think otherwise
| queue.Send(transactionReference, verifyCandidate, authReceiptCh) | ||
| queue.Send(transactionReference, *candidate, batchReceiptCh) |
There was a problem hiding this comment.
M3 — Nonce-gap hazard when the auth send fails asynchronously.
These two Sends craft consecutive nonces synchronously (auth = N, batch = N+1). If the auth tx is crafted but its async sendTx later fails, txmgr calls resetNonce() (op-service/txmgr/txmgr.go:372) while the batch tx at nonce N+1 is already in flight — leaving a permanent nonce gap that wedges the sender until the cancel/gap-fill path runs.
This is the same class as the upstream single-tx path, but amplified here because this path deliberately fires two ordered txs and a failure/revert on the first is now an expected case (e.g. the activeIsEspresso-still-true scenario). Worth a targeted integration test against the real txmgr.Queue; consider gating the batch Send on auth success rather than firing both unconditionally.
There was a problem hiding this comment.
Consider the transactions for 2 subsequent batches of batches
A1 - Auth tx batch 1
B1 - Batch tx batch 1
A2 - ...
B2 - ...
Assuming A1 fails and causes the nonce to be reset B1s nonce will be too high and prevent it from getting mined.
However watchFallbackAuthReceipts doesn't block the next iteration of the publishingLoop so the loop will proceed to submit A2 and B2.
A2 should work because it would have a correct nonce, but the nonce of B2 would clash with B1.
Assuming that B2 succeeds B1 will now fail due to it's nonce being too low. Once B1 is dropped the synthetic failure receipt will be sent on receptsCh causing the channel to rewind to just before when A1 was sent.
If A & B were both in the same channel then they will just be submitted again and the previously submitted parts of A & B will be lost, but if they are separate channels and now B has arrived before A, then the batcher will ultimately end up having to rewind to the last safe head when it decides that the safe head is not progressing appropriately.
I think this can be pretty easily fixed by simply submitting and waiting for the auth tx before submitting and waiting for the batch tx and stopping and returning a synthetic error receipt at the first error encountered.
There was a problem hiding this comment.
I believe I understand the issue, I will make a separate PR on top of this to address it so it is easier to review.
| if err != nil { | ||
| return false, fmt.Errorf("failed to fetch L1 tip for fallback-auth gate: %w", err) | ||
| } | ||
| leadSec := uint64(l.Config.FallbackAuthLeadTime / time.Second) |
There was a problem hiding this comment.
M4 — FallbackAuthLeadTime is unvalidated; negative or zero is unsafe.
CLIConfig.Check() (op-batcher/batcher/config.go) never validates this flag. A negative duration makes uint64(... / time.Second) wrap to a huge value, so tip.Time + leadSec overflows and the gate misfires. A value of 0 removes the safety margin entirely and reintroduces the boundary-window silent drop the lead time exists to prevent: a tx decided pre-fork that lands in a post-fork block makes the verifier require a BatchInfoAuthenticated event the batcher never sent → batch silently dropped.
Recommend validating in Check(): reject negative, and reject (or at least warn) on 0; document that the lead time must exceed the worst-case L1 inclusion delay.
There was a problem hiding this comment.
Added validation for anything <= 0. And updated the documentation. Let me know if this looks correct
02831d8
There was a problem hiding this comment.
Actually reading Piers comment below, i may look to remove this.
| // FallbackAuthLeadTime is the lead time for the fallback batcher's | ||
| // authentication gate. See BatcherConfig.FallbackAuthLeadTime in | ||
| // service.go and isFallbackAuthRequired in espresso_active.go. | ||
| FallbackAuthLeadTime time.Duration |
There was a problem hiding this comment.
If I understand correctly this lead time is meant to account for the time difference between the block at which a batch is authenticated and the block at which the batch is submitted to the batcher address.
If so, there is already an upper bound for that inclusion delay, it's the 100 block lookback window. So can't this just be set to a constant and removed from the config?
There was a problem hiding this comment.
Or simpler still, remove this entirely and have the derivation pipeline require espresso activation to be at least 100 blocks old before switching to espresso verification, thus allowing for a full lookback window. This keeps the 100 block window contained within derivation rather than leaking it here.
There was a problem hiding this comment.
Regarding this solution, i took a stab at it, but I think this changes some small code in the derivation pipeline, which is now being audited. So I am unsure if we want to make these changes now.
| if !l.hasBatchAuthenticator() { | ||
| return false | ||
| } |
There was a problem hiding this comment.
This check could be moved inside isFallbackAuthRequired, it seems it's only used there
There was a problem hiding this comment.
removed the method and put the guard inside isFallbackAuthRequired
25c5086
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76b729b25b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02831d8ad7
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Nothing instantiates a bound contract from L1Client; the fallback-auth path only uses the package-level BatchAuthenticatorMetaData.GetAbi(), which needs no backend. Removing the requirement narrows the interface and lets fakeL1Client drop its nil ContractBackend embed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
computeCommitment reimplemented the verifier's batch-hash logic, so the two could drift and silently drop post-fork batches. Delegate to derive.ComputeCalldataBatchHash / ComputeBlobBatchHash instead, and add a parity test that checks both paths against those functions — the blob path using the real versioned hashes from txmgr.MakeSidecar (what the verifier reads from tx.BlobHashes()). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reference the real verifier gate rollupCfg.IsEspresso(l1OriginTime) instead of the non-existent DataSourceConfig.isEspressoEnforcement, and drop the misleading claim that authenticateBatchInfo is gated because it 'would revert against the default activeIsEspresso=true contract state'. That activeIsEspresso switch is an independent guardian-set mode, not a fork invariant; the verifier-gate reason alone is the correct one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
authGroup was an errgroup.Group whose goroutines always returned nil (failures are reported via receiptsCh, not the group error), so the error branch in waitForAuthGroup was unreachable. Switch to a plain sync.WaitGroup, drop the dead error handling, and correct the field comment: watcher creation is back-pressured by, not hard-bounded by, MaxPendingTransactions. Also document the receipts-loop-outlives-authGroup invariant that keeps the final receiptsCh send from blocking. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The txRef literal (id/isCancel/isBlob/daType/size from a txData) was duplicated across sendTx, the fallback-auth gate error path, and the fallback-auth submission. Extract newTxRef(txdata, isCancel) so the three sites stay in sync. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 25c5086252
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if authResult.Err != nil { | ||
| l.Log.Error("Failed to send fallback authenticateBatchInfo transaction", "txRef", transactionReference, "err", authResult.Err) | ||
| receiptsCh <- txmgr.TxReceipt[txRef]{ | ||
| ID: transactionReference, |
There was a problem hiding this comment.
Preserve the auth tx type when reporting auth send failures
When a fallback-auth blob batch hits ErrAlreadyReserved while sending the non-blob authenticateBatchInfo tx (for example with --max-pending-tx > 1 or an existing blob reservation), this synthetic receipt reports the blob batch's txRef instead of the auth tx's actual non-blob type. The receipts loop uses r.ID.isBlob to choose the cancellation transaction, so it will try a calldata cancellation after a non-blob auth send was rejected by a blob reservation, rather than sending a blob replacement to clear the reserved blob; the batcher can then keep requeueing without unblocking the txpool.
Useful? React with 👍 / 👎.
Relatively small change based on #445 - makes fallback batcher authenticate its transactions.
This is #448, re-hosted from an in-repo branch (now properly stacked).