-
Notifications
You must be signed in to change notification settings - Fork 14
Espresso 3b: TEE batcher (re-hosted) #459
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
Changes from 48 commits
89764d5
f3eb733
5104c13
2a474c2
999c8aa
3a632b8
c4f4387
46bd235
7456c59
ec94e36
e3953ad
0cbefe2
5048204
c473297
a36d1bc
5f59a21
152f1cf
952bd2c
c5451c8
04e1a07
0328723
36697eb
7d16631
34c3902
223281f
414ee4c
199c27f
dfed09b
d50e704
aac06fc
b48d811
696aeb9
cc32692
790b12e
d15aa5f
6c02fe6
09485ba
9b4d472
1cb8275
e6fdd6b
abe354d
94cf62b
cf7a5c8
f8da3d8
608840b
43f2cd5
2743226
6e86950
72dd970
c153753
af22822
12fe8b1
7548cc7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,227 @@ | ||
| package espresso | ||
|
|
||
| import ( | ||
| "crypto/ecdsa" | ||
| "fmt" | ||
| "strings" | ||
| "time" | ||
|
|
||
| "github.com/ethereum/go-ethereum/common" | ||
| "github.com/ethereum/go-ethereum/crypto" | ||
|
|
||
| "github.com/urfave/cli/v2" | ||
| ) | ||
|
|
||
| // 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") | ||
| AttestationServiceFlagName = espressoFlags("espresso-attestation-service") | ||
| 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 the Espresso light client is deployed on; defaults to the batcher's L1 RPC when unset", | ||
| 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; set it explicitly when " + | ||
| "taking over from the fallback batcher. " + | ||
| "When zero, no floor is enforced and the streamer anchors at the current local-safe head. " + | ||
| "Independent of the EspressoTime hardfork, which gates derivation semantics.", | ||
| Value: 0, | ||
| EnvVars: espressoEnvs(envPrefix, "ORIGIN_HEIGHT_L2"), | ||
| Category: category, | ||
| }, | ||
| &cli.StringFlag{ | ||
| Name: AttestationServiceFlagName, | ||
| Usage: "URL of the Espresso attestation service", | ||
| EnvVars: espressoEnvs(envPrefix, "ESPRESSO_ATTESTATION_SERVICE"), | ||
| 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 | ||
| L1URL string | ||
| TestingBatcherPrivateKey *ecdsa.PrivateKey | ||
| 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 { | ||
|
philippecamacho marked this conversation as resolved.
|
||
| // 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.allowEmptyAttestationService && c.EspressoAttestationService == "" { | ||
| return fmt.Errorf("attestation service URL is required when Espresso is enabled") | ||
| } | ||
|
Comment on lines
+168
to
+170
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Espresso runs outside Nitro with Useful? React with 👍 / 👎. |
||
| if c.PollInterval <= 0 { | ||
| return fmt.Errorf("poll interval must be > 0") | ||
| } | ||
| 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), | ||
| 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) | ||
|
|
||
| pkStr := c.String(TestingBatcherPrivateKeyFlagName) | ||
| pkStr = strings.TrimPrefix(pkStr, "0x") | ||
| pk, err := crypto.HexToECDSA(pkStr) | ||
| if err == nil { | ||
| config.TestingBatcherPrivateKey = pk | ||
| } | ||
|
|
||
| return config | ||
| } | ||
| 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 | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.