forked from ethereum-optimism/optimism
-
Notifications
You must be signed in to change notification settings - Fork 12
Espresso 3b: TEE batcher #447
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
49 commits
Select commit
Hold shift + click to select a range
fdfc7e5
contracts-bedrock: add Espresso BatchAuthenticator and supporting infra
QuentinI 25f2234
Update packages/contracts-bedrock/src/L1/BatchAuthenticator.sol
QuentinI 8b76632
Remove stray path
QuentinI 97d4a34
Remove hardcoded Sepolia URL
QuentinI 857717f
Use OZ v5 in mock verifier
QuentinI 00d6510
Fix Codex's suggestion
QuentinI cabd2ee
Lower pragma
QuentinI 7ea3c59
Remove unrelated foundry.toml changes
QuentinI 5e4bcf8
scripts/checks: clean up exclude lists per PR review
QuentinI f84ac86
Fix re-initialization
QuentinI 6b85fa1
Add batcher address history
QuentinI a882446
contracts-bedrock: drop OZ TUP from espresso deploy
palango c8db328
Remove pause from BatchAuthenticator
QuentinI 8630695
Add a defensive check
QuentinI 353252f
test: add end-to-end dual-batcher switch test
QuentinI de02776
Fix tests
QuentinI 0ccf6ec
switchBatcher from toggle to a setter
QuentinI d9a4585
Use OZ Checkpoints for batcher history
QuentinI 3b4618a
contracts-bedrock: deploy espresso impls via vm.getCode, drop suppres…
QuentinI 4821ebe
Check batcher in Espresso mode
shenkeyao c67ab4d
regenerate snapshots for UnauthorizedEspressoBatcher error
QuentinI d2ed3bc
contracts-bedrock: wire espresso proxies to shared OP Stack ProxyAdmin
QuentinI 12048f9
add caller to BatchInfoAuthenticated event
QuentinI 9448a74
forge fmt
piersy 05fd400
Remove unused imports
piersy eeed5c2
Rename tests to fit test name convention
piersy 308f90f
op-chain-ops/script: resolve directory-qualified getCode artifact names
QuentinI 6fd5401
contracts-bedrock: fix semgrep checks-fast findings
QuentinI 74deff1
op-node: add event-based batch authentication
QuentinI c8bf60a
op-node: rename EspressoEnforcementTime to EspressoTime
QuentinI 9b368a0
op-node: require BatchAuthenticatorAddress when Espresso is enabled
QuentinI 2c8a2b2
op-node: fix EspressoTime fork-activation doc comments
QuentinI 17fa0aa
op-node: bind authenticated batches to the authenticating caller
QuentinI e97ece8
op-node: replace global batch auth caches with dependency-injected in…
piersy 7d05d6c
Use rollup.Config in DataSourceConfig
piersy 9c017c3
op-node: fix up batch-auth cache DI and rollupCfg cherry-picks
QuentinI 931a7dd
Hardcode BatchAuthLookbackWindow
QuentinI beb1b33
Tighten assertions for batch auth tests
piersy 231bdbc
op-node: test Espresso fork boundary and per-commitment batch auth
piersy 195a1df
op-node: isolate Espresso derivation tests into espresso_-prefixed files
piersy 0787bc5
Update op-node/rollup/derive/espresso_blob_data_source_test.go
piersy f37b39e
Update op-node/rollup/derive/espresso_blob_data_source_test.go
piersy 61b294c
espresso/bindings: add regenerated BatchAuthenticator Go bindings
QuentinI 1119193
op-batcher: integrate fallback batcher authentication
QuentinI 1f91ece
op-service: add ChainSigner interface and eth_sign helper
QuentinI a34c67b
op-node: add EspressoBatch type and converters
QuentinI 2ae2120
espresso: add TEE batcher CLI flags, streamer interface, and L1 adapter
QuentinI 493cb2b
op-batcher: add Nitro NSM attestation helper
QuentinI 7e31775
op-batcher: integrate TEE batcher and Espresso submission loop
QuentinI File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Large diffs are not rendered by default.
Oops, something went wrong.
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,306 @@ | ||
| package espresso | ||
|
|
||
| import ( | ||
| "crypto/ecdsa" | ||
| "fmt" | ||
| "strings" | ||
| "time" | ||
|
|
||
| op "github.com/EspressoSystems/espresso-streamers/op" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
| "github.com/ethereum/go-ethereum/ethclient" | ||
| "github.com/ethereum/go-ethereum/log" | ||
|
|
||
| "github.com/urfave/cli/v2" | ||
|
|
||
| espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" | ||
| espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" | ||
| ) | ||
|
|
||
| // espressoFlags returns the flag names for espresso | ||
| func espressoFlags(v string) string { | ||
| return "espresso." + v | ||
| } | ||
|
|
||
| func espressoEnvs(envprefix, v string) []string { | ||
| return []string{envprefix + "_ESPRESSO_" + v} | ||
| } | ||
|
|
||
| // Default values for batch submission receipt verification tuning. | ||
| // Defined here so that both the CLI flag defaults and the batcher logic | ||
| // can reference a single source of truth. | ||
| // | ||
| // Note: DefaultBatchAuthLookbackWindow lives in constants.go (mips64-clean | ||
| // build target shared with the derivation pipeline). | ||
| const ( | ||
| DefaultVerifyReceiptMaxBlocks uint64 = 5 | ||
| DefaultVerifyReceiptSafetyTimeout time.Duration = 5 * time.Minute | ||
| DefaultVerifyReceiptRetryDelay time.Duration = 100 * time.Millisecond | ||
| DefaultMaxInFlightRequestsToEspresso = 128 | ||
| ) | ||
|
|
||
| var ( | ||
| EnabledFlagName = espressoFlags("enabled") | ||
| PollIntervalFlagName = espressoFlags("poll-interval") | ||
| QueryServiceUrlsFlagName = espressoFlags("urls") | ||
| LightClientAddrFlagName = espressoFlags("light-client-addr") | ||
| L1UrlFlagName = espressoFlags("l1-url") | ||
| TestingBatcherPrivateKeyFlagName = espressoFlags("testing-batcher-private-key") | ||
| CaffeinationHeightEspresso = espressoFlags("origin-height-espresso") | ||
| CaffeinationHeightL2 = espressoFlags("origin-height-l2") | ||
| NamespaceFlagName = espressoFlags("namespace") | ||
| RollupL1UrlFlagName = espressoFlags("rollup-l1-url") | ||
| AttestationServiceFlagName = espressoFlags("espresso-attestation-service") | ||
| BatchAuthenticatorAddrFlagName = espressoFlags("batch-authenticator-addr") | ||
| VerifyReceiptMaxBlocksFlagName = espressoFlags("verify-receipt-max-blocks") | ||
| VerifyReceiptSafetyTimeoutFlagName = espressoFlags("verify-receipt-safety-timeout") | ||
| VerifyReceiptRetryDelayFlagName = espressoFlags("verify-receipt-retry-delay") | ||
| ) | ||
|
|
||
| func CLIFlags(envPrefix string, category string) []cli.Flag { | ||
| return []cli.Flag{ | ||
| &cli.BoolFlag{ | ||
| Name: EnabledFlagName, | ||
| Usage: "Enable Espresso mode", | ||
| Value: false, | ||
| EnvVars: espressoEnvs(envPrefix, "ENABLED"), | ||
| Category: category, | ||
| }, | ||
| &cli.DurationFlag{ | ||
| Name: PollIntervalFlagName, | ||
| Usage: "Polling interval for Espresso queries", | ||
| Value: 250 * time.Millisecond, | ||
| EnvVars: espressoEnvs(envPrefix, "POLL_INTERVAL"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringSliceFlag{ | ||
| Name: QueryServiceUrlsFlagName, | ||
| Usage: "Comma-separated list of Espresso query service URLs", | ||
| EnvVars: espressoEnvs(envPrefix, "URLS"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: LightClientAddrFlagName, | ||
| Usage: "Address of the Espresso light client", | ||
| EnvVars: espressoEnvs(envPrefix, "LIGHT_CLIENT_ADDR"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: L1UrlFlagName, | ||
| Usage: "L1 RPC URL Espresso contracts are deployed on", | ||
| EnvVars: espressoEnvs(envPrefix, "L1_URL"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: TestingBatcherPrivateKeyFlagName, | ||
| Usage: "Pre-approved batcher ephemeral key (testing only)", | ||
| EnvVars: espressoEnvs(envPrefix, "TESTING_BATCHER_PRIVATE_KEY"), | ||
| Category: category, | ||
| }, | ||
| &cli.Uint64Flag{ | ||
| Name: CaffeinationHeightEspresso, | ||
| Usage: "Espresso transactions below this height will not be considered", | ||
| EnvVars: espressoEnvs(envPrefix, "ORIGIN_HEIGHT_ESPRESSO"), | ||
| Category: category, | ||
| }, | ||
| &cli.Uint64Flag{ | ||
| Name: CaffeinationHeightL2, | ||
| Usage: "L2 batch position at which the Espresso streamer starts emitting batches. " + | ||
| "Operational parameter for restarting batchers mid-chain. " + | ||
| "When zero, the streamer falls back to its internal default. " + | ||
| "Independent of the EspressoTime hardfork, which gates derivation semantics.", | ||
| Value: 0, | ||
| EnvVars: espressoEnvs(envPrefix, "ORIGIN_HEIGHT_L2"), | ||
| Category: category, | ||
| }, | ||
| &cli.Uint64Flag{ | ||
| Name: NamespaceFlagName, | ||
| Usage: "Namespace of Espresso transactions", | ||
| EnvVars: espressoEnvs(envPrefix, "NAMESPACE"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: RollupL1UrlFlagName, | ||
| Usage: "RPC URL of L1 backing the Rollup we're streaming for", | ||
| EnvVars: espressoEnvs(envPrefix, "ROLLUP_L1_URL"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: AttestationServiceFlagName, | ||
| Usage: "URL of the Espresso attestation service", | ||
| EnvVars: espressoEnvs(envPrefix, "ESPRESSO_ATTESTATION_SERVICE"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: BatchAuthenticatorAddrFlagName, | ||
| Usage: "Address of the Batch Authenticator contract", | ||
| EnvVars: espressoEnvs(envPrefix, "BATCH_AUTHENTICATOR_ADDR"), | ||
| Category: category, | ||
| }, | ||
| &cli.Uint64Flag{ | ||
| Name: VerifyReceiptMaxBlocksFlagName, | ||
| Usage: "Number of HotShot blocks to wait for a submitted transaction to become queryable before re-submitting", | ||
| Value: DefaultVerifyReceiptMaxBlocks, | ||
| EnvVars: espressoEnvs(envPrefix, "VERIFY_RECEIPT_MAX_BLOCKS"), | ||
| Category: category, | ||
| }, | ||
| &cli.DurationFlag{ | ||
| Name: VerifyReceiptSafetyTimeoutFlagName, | ||
| Usage: "Wall-clock backstop for receipt verification; re-submits the transaction if this duration is exceeded", | ||
| Value: DefaultVerifyReceiptSafetyTimeout, | ||
| EnvVars: espressoEnvs(envPrefix, "VERIFY_RECEIPT_SAFETY_TIMEOUT"), | ||
| Category: category, | ||
| }, | ||
| &cli.DurationFlag{ | ||
| Name: VerifyReceiptRetryDelayFlagName, | ||
| Usage: "Delay between receipt verification retries", | ||
| Value: DefaultVerifyReceiptRetryDelay, | ||
| EnvVars: espressoEnvs(envPrefix, "VERIFY_RECEIPT_RETRY_DELAY"), | ||
| Category: category, | ||
| }, | ||
| // Note: --espresso.fallback-auth-lead-time is registered by the | ||
| // fallback batcher in op-batcher/flags/flags.go; it is read by both | ||
| // the fallback and the TEE batcher paths. | ||
| } | ||
| } | ||
|
|
||
| type CLIConfig struct { | ||
| Enabled bool | ||
| PollInterval time.Duration | ||
| QueryServiceURLs []string | ||
| LightClientAddr common.Address | ||
| BatchAuthenticatorAddr common.Address | ||
| L1URL string | ||
| RollupL1URL string | ||
| TestingBatcherPrivateKey *ecdsa.PrivateKey | ||
| Namespace uint64 | ||
| CaffeinationHeightEspresso uint64 | ||
| CaffeinationHeightL2 uint64 | ||
| EspressoAttestationService string | ||
|
|
||
| // Batch submission receipt verification tuning | ||
| VerifyReceiptMaxBlocks uint64 | ||
| VerifyReceiptSafetyTimeout time.Duration | ||
| VerifyReceiptRetryDelay time.Duration | ||
|
|
||
| // Non directly configurable option | ||
| allowEmptyAttestationService bool `json:"-"` | ||
| } | ||
|
|
||
| // AllowEmptyAttestationService allows the attestation service URL to be | ||
| // empty. This is set explicitly from a public method, and isn't derivable | ||
| // from serialization or any other form other than this method. This allows | ||
| // this setting to be configured via the code, but not externally. | ||
| func (c *CLIConfig) AllowEmptyAttestationService() { | ||
| c.allowEmptyAttestationService = true | ||
| } | ||
|
|
||
| func (c CLIConfig) Check() error { | ||
| if c.Enabled { | ||
| // Check required fields when Espresso is enabled | ||
| if len(c.QueryServiceURLs) == 0 { | ||
| return fmt.Errorf("query service URLs are required when Espresso is enabled") | ||
| } | ||
| if c.LightClientAddr == (common.Address{}) { | ||
| return fmt.Errorf("light client address is required when Espresso is enabled") | ||
| } | ||
| if c.L1URL == "" { | ||
| return fmt.Errorf("L1 URL is required when Espresso is enabled") | ||
| } | ||
| if c.RollupL1URL == "" { | ||
| return fmt.Errorf("rollup L1 URL is required when Espresso is enabled") | ||
| } | ||
| if c.Namespace == 0 { | ||
| return fmt.Errorf("namespace is required when Espresso is enabled") | ||
| } | ||
| if !c.allowEmptyAttestationService && c.EspressoAttestationService == "" { | ||
| return fmt.Errorf("attestation service URL is required when Espresso is enabled") | ||
| } | ||
| if c.VerifyReceiptMaxBlocks == 0 { | ||
| return fmt.Errorf("verify-receipt-max-blocks must be > 0") | ||
| } | ||
| if c.VerifyReceiptSafetyTimeout <= 0 { | ||
| return fmt.Errorf("verify-receipt-safety-timeout must be > 0") | ||
| } | ||
| if c.VerifyReceiptRetryDelay <= 0 { | ||
| return fmt.Errorf("verify-receipt-retry-delay must be > 0") | ||
| } | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func ReadCLIConfig(c *cli.Context) CLIConfig { | ||
| config := CLIConfig{ | ||
| Enabled: c.Bool(EnabledFlagName), | ||
| PollInterval: c.Duration(PollIntervalFlagName), | ||
| L1URL: c.String(L1UrlFlagName), | ||
| RollupL1URL: c.String(RollupL1UrlFlagName), | ||
| Namespace: c.Uint64(NamespaceFlagName), | ||
| CaffeinationHeightEspresso: c.Uint64(CaffeinationHeightEspresso), | ||
| CaffeinationHeightL2: c.Uint64(CaffeinationHeightL2), | ||
| EspressoAttestationService: c.String(AttestationServiceFlagName), | ||
| VerifyReceiptMaxBlocks: c.Uint64(VerifyReceiptMaxBlocksFlagName), | ||
| VerifyReceiptSafetyTimeout: c.Duration(VerifyReceiptSafetyTimeoutFlagName), | ||
| VerifyReceiptRetryDelay: c.Duration(VerifyReceiptRetryDelayFlagName), | ||
| } | ||
|
|
||
| config.QueryServiceURLs = c.StringSlice(QueryServiceUrlsFlagName) | ||
|
|
||
| addrStr := c.String(LightClientAddrFlagName) | ||
| config.LightClientAddr = common.HexToAddress(addrStr) | ||
|
|
||
| batchAuthenticatorAddrStr := c.String(BatchAuthenticatorAddrFlagName) | ||
| config.BatchAuthenticatorAddr = common.HexToAddress(batchAuthenticatorAddrStr) | ||
|
|
||
| pkStr := c.String(TestingBatcherPrivateKeyFlagName) | ||
| pkStr = strings.TrimPrefix(pkStr, "0x") | ||
| pk, err := crypto.HexToECDSA(pkStr) | ||
| if err == nil { | ||
| config.TestingBatcherPrivateKey = pk | ||
| } | ||
|
|
||
| return config | ||
| } | ||
|
|
||
| func BatchStreamerFromCLIConfig[B op.Batch]( | ||
| cfg CLIConfig, | ||
| log log.Logger, | ||
| unmarshalBatch func([]byte) (*B, error), | ||
| ) (*op.BatchStreamer[B], error) { | ||
| if !cfg.Enabled { | ||
| return nil, fmt.Errorf("espresso is not enabled") | ||
| } | ||
|
|
||
| l1Client, err := ethclient.Dial(cfg.L1URL) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to dial L1 RPC at %s: %w", cfg.L1URL, err) | ||
| } | ||
|
|
||
| RollupL1Client, err := ethclient.Dial(cfg.RollupL1URL) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to dial Rollup L1 RPC at %s: %w", cfg.RollupL1URL, err) | ||
| } | ||
|
|
||
| urlZero := cfg.QueryServiceURLs[0] | ||
| espressoClient := espressoClient.NewClient(urlZero) | ||
|
|
||
| espressoLightClient, err := espressoLightClient.NewLightclientCaller(cfg.LightClientAddr, l1Client) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to create Espresso light client") | ||
| } | ||
|
|
||
| return op.NewEspressoStreamer( | ||
| cfg.Namespace, | ||
| NewAdaptL1BlockRefClient(l1Client), | ||
| NewAdaptL1BlockRefClient(RollupL1Client), | ||
| espressoClient, | ||
| espressoLightClient, | ||
| log, | ||
| unmarshalBatch, | ||
| cfg.CaffeinationHeightEspresso, | ||
| cfg.CaffeinationHeightL2, | ||
| cfg.BatchAuthenticatorAddr, | ||
| false, | ||
| ) | ||
| } | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| package espresso | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "math/big" | ||
|
|
||
| "github.com/ethereum/go-ethereum" | ||
| "github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/ethclient" | ||
|
|
||
| "github.com/ethereum-optimism/optimism/espresso/bindings" | ||
| ) | ||
|
|
||
| // AdaptL1BlockRefClient is a wrapper around eth.L1BlockRef that implements the espresso.L1Client interface | ||
| type AdaptL1BlockRefClient struct { | ||
| L1Client *ethclient.Client | ||
| } | ||
|
|
||
| // NewAdaptL1BlockRefClient creates a new L1BlockRefClient | ||
| func NewAdaptL1BlockRefClient(L1Client *ethclient.Client) *AdaptL1BlockRefClient { | ||
| return &AdaptL1BlockRefClient{ | ||
| L1Client: L1Client, | ||
| } | ||
| } | ||
|
|
||
| // HeaderHashByNumber implements the espresso.L1Client interface | ||
| func (c *AdaptL1BlockRefClient) HeaderHashByNumber(ctx context.Context, number *big.Int) (common.Hash, error) { | ||
| expectedL1BlockRef, err := c.L1Client.HeaderByNumber(ctx, number) | ||
| if err != nil { | ||
| return common.Hash{}, err | ||
| } | ||
|
|
||
| return expectedL1BlockRef.Hash(), nil | ||
| } | ||
|
|
||
| func (c *AdaptL1BlockRefClient) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { | ||
| return c.L1Client.CodeAt(ctx, contract, blockNumber) | ||
| } | ||
|
|
||
| func (c *AdaptL1BlockRefClient) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { | ||
| return c.L1Client.CallContract(ctx, call, blockNumber) | ||
| } | ||
|
|
||
| // FetchEspressoBatcherAddress reads the Espresso batcher address from the BatchAuthenticator | ||
| // contract on L1. This is used by the caff node to determine which address signed | ||
| // Espresso batches, since the Espresso batcher may use a different key than the | ||
| // SystemConfig batcher (fallback batcher). | ||
| func FetchEspressoBatcherAddress(ctx context.Context, l1Client *ethclient.Client, batchAuthenticatorAddr common.Address) (common.Address, error) { | ||
| caller, err := bindings.NewBatchAuthenticatorCaller(batchAuthenticatorAddr, l1Client) | ||
| if err != nil { | ||
| return common.Address{}, fmt.Errorf("failed to bind BatchAuthenticator at %s: %w", batchAuthenticatorAddr, err) | ||
| } | ||
| addr, err := caller.EspressoBatcher(&bind.CallOpts{Context: ctx}) | ||
| if err != nil { | ||
| return common.Address{}, fmt.Errorf("failed to call BatchAuthenticator.espressoBatcher(): %w", err) | ||
| } | ||
| return addr, nil | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FallbackAuthLeadTimeis validated only whenc.Enabledis true, but fallback mode (--espresso.enabled=false) still uses this field inisFallbackAuthRequired; a negative duration therefore bypasses validation and is later cast touint64, producing a huge lead that can force premature/always-on fallback auth gating. This creates hard-to-diagnose behavior for fallback batchers with a mis-set duration flag.Useful? React with 👍 / 👎.