diff --git a/espresso/bindings/system_config.go b/espresso/bindings/system_config.go new file mode 100644 index 00000000000..0498555e156 --- /dev/null +++ b/espresso/bindings/system_config.go @@ -0,0 +1,2640 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package bindings + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription + _ = abi.ConvertType +) + +// IResourceMeteringResourceConfig is an auto generated low-level Go binding around an user-defined struct. +type IResourceMeteringResourceConfig struct { + MaxResourceLimit uint32 + ElasticityMultiplier uint8 + BaseFeeMaxChangeDenominator uint8 + MinimumBaseFee uint32 + SystemTxMaxGas uint32 + MaximumBaseFee *big.Int +} + +// SystemConfigAddresses is an auto generated low-level Go binding around an user-defined struct. +type SystemConfigAddresses struct { + L1CrossDomainMessenger common.Address + L1ERC721Bridge common.Address + L1StandardBridge common.Address + OptimismPortal common.Address + OptimismMintableERC20Factory common.Address + DelayedWETH common.Address + Opcm common.Address +} + +// SystemConfigMetaData contains all meta data concerning the SystemConfig contract. +var SystemConfigMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"BATCH_INBOX_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"DELAYED_WETH_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_CROSS_DOMAIN_MESSENGER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_ERC_721_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"L1_STANDARD_BRIDGE_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPCM_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"OPTIMISM_PORTAL_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"START_BLOCK_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"UNSAFE_BLOCK_SIGNER_SLOT\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"VERSION\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"basefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batchInbox\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"batcherHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"blobbasefeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"daFootprintGasScalar\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"\",\"type\":\"uint16\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"delayedWETH\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"disputeGameFactory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip1559Denominator\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"eip1559Elasticity\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAddresses\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1ERC721Bridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1StandardBridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismPortal\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"delayedWETH\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"opcm\",\"type\":\"address\"}],\"internalType\":\"structSystemConfig.Addresses\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"guardian\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initVersion\",\"outputs\":[{\"internalType\":\"uint8\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"},{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structIResourceMetering.ResourceConfig\",\"name\":\"_config\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_batchInbox\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"address\",\"name\":\"l1CrossDomainMessenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1ERC721Bridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"l1StandardBridge\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismPortal\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"optimismMintableERC20Factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"delayedWETH\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"opcm\",\"type\":\"address\"}],\"internalType\":\"structSystemConfig.Addresses\",\"name\":\"_addresses\",\"type\":\"tuple\"},{\"internalType\":\"uint256\",\"name\":\"_l2ChainId\",\"type\":\"uint256\"},{\"internalType\":\"contractISuperchainConfig\",\"name\":\"_superchainConfig\",\"type\":\"address\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isCustomGasToken\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"name\":\"isFeatureEnabled\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1CrossDomainMessenger\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1ERC721Bridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l1StandardBridge\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l2ChainId\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastUsedOPCM\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lastUsedOPCMVersion\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"version_\",\"type\":\"string\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maximumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minBaseFee\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minimumGasLimit\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operatorFeeConstant\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"operatorFeeScalar\",\"outputs\":[{\"internalType\":\"uint32\",\"name\":\"\",\"type\":\"uint32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismMintableERC20Factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"optimismPortal\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"overhead\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxyAdmin\",\"outputs\":[{\"internalType\":\"contractIProxyAdmin\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"proxyAdminOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"resourceConfig\",\"outputs\":[{\"components\":[{\"internalType\":\"uint32\",\"name\":\"maxResourceLimit\",\"type\":\"uint32\"},{\"internalType\":\"uint8\",\"name\":\"elasticityMultiplier\",\"type\":\"uint8\"},{\"internalType\":\"uint8\",\"name\":\"baseFeeMaxChangeDenominator\",\"type\":\"uint8\"},{\"internalType\":\"uint32\",\"name\":\"minimumBaseFee\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"systemTxMaxGas\",\"type\":\"uint32\"},{\"internalType\":\"uint128\",\"name\":\"maximumBaseFee\",\"type\":\"uint128\"}],\"internalType\":\"structIResourceMetering.ResourceConfig\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scalar\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_batcher\",\"type\":\"address\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_batcherHash\",\"type\":\"bytes32\"}],\"name\":\"setBatcherHash\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint16\",\"name\":\"_daFootprintGasScalar\",\"type\":\"uint16\"}],\"name\":\"setDAFootprintGasScalar\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_denominator\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_elasticity\",\"type\":\"uint32\"}],\"name\":\"setEIP1559Params\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_feature\",\"type\":\"bytes32\"},{\"internalType\":\"bool\",\"name\":\"_enabled\",\"type\":\"bool\"}],\"name\":\"setFeature\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_overhead\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_scalar\",\"type\":\"uint256\"}],\"name\":\"setGasConfig\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_basefeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"_blobbasefeeScalar\",\"type\":\"uint32\"}],\"name\":\"setGasConfigEcotone\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_gasLimit\",\"type\":\"uint64\"}],\"name\":\"setGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint64\",\"name\":\"_minBaseFee\",\"type\":\"uint64\"}],\"name\":\"setMinBaseFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint32\",\"name\":\"_operatorFeeScalar\",\"type\":\"uint32\"},{\"internalType\":\"uint64\",\"name\":\"_operatorFeeConstant\",\"type\":\"uint64\"}],\"name\":\"setOperatorFeeScalars\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_unsafeBlockSigner\",\"type\":\"address\"}],\"name\":\"setUnsafeBlockSigner\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"startBlock\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"startBlock_\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"superchainConfig\",\"outputs\":[{\"internalType\":\"contractISuperchainConfig\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unsafeBlockSigner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"addr_\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"\",\"type\":\"string\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"version\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"enumSystemConfig.UpdateType\",\"name\":\"updateType\",\"type\":\"uint8\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"ConfigUpdate\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"feature\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bool\",\"name\":\"enabled\",\"type\":\"bool\"}],\"name\":\"FeatureSet\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_NotProxyAdmin\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_NotProxyAdminOrProxyAdminOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_NotProxyAdminOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_NotResolvedDelegateProxy\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_NotSharedProxyAdminOwner\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ProxyAdminOwnedBase_ProxyAdminNotFound\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"ReinitializableBase_ZeroInitVersion\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"SystemConfig_InvalidFeatureState\",\"type\":\"error\"}]", +} + +// SystemConfigABI is the input ABI used to generate the binding from. +// Deprecated: Use SystemConfigMetaData.ABI instead. +var SystemConfigABI = SystemConfigMetaData.ABI + +// SystemConfig is an auto generated Go binding around an Ethereum contract. +type SystemConfig struct { + SystemConfigCaller // Read-only binding to the contract + SystemConfigTransactor // Write-only binding to the contract + SystemConfigFilterer // Log filterer for contract events +} + +// SystemConfigCaller is an auto generated read-only Go binding around an Ethereum contract. +type SystemConfigCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemConfigTransactor is an auto generated write-only Go binding around an Ethereum contract. +type SystemConfigTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemConfigFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type SystemConfigFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// SystemConfigSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type SystemConfigSession struct { + Contract *SystemConfig // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SystemConfigCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type SystemConfigCallerSession struct { + Contract *SystemConfigCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// SystemConfigTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type SystemConfigTransactorSession struct { + Contract *SystemConfigTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// SystemConfigRaw is an auto generated low-level Go binding around an Ethereum contract. +type SystemConfigRaw struct { + Contract *SystemConfig // Generic contract binding to access the raw methods on +} + +// SystemConfigCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type SystemConfigCallerRaw struct { + Contract *SystemConfigCaller // Generic read-only contract binding to access the raw methods on +} + +// SystemConfigTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type SystemConfigTransactorRaw struct { + Contract *SystemConfigTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewSystemConfig creates a new instance of SystemConfig, bound to a specific deployed contract. +func NewSystemConfig(address common.Address, backend bind.ContractBackend) (*SystemConfig, error) { + contract, err := bindSystemConfig(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &SystemConfig{SystemConfigCaller: SystemConfigCaller{contract: contract}, SystemConfigTransactor: SystemConfigTransactor{contract: contract}, SystemConfigFilterer: SystemConfigFilterer{contract: contract}}, nil +} + +// NewSystemConfigCaller creates a new read-only instance of SystemConfig, bound to a specific deployed contract. +func NewSystemConfigCaller(address common.Address, caller bind.ContractCaller) (*SystemConfigCaller, error) { + contract, err := bindSystemConfig(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &SystemConfigCaller{contract: contract}, nil +} + +// NewSystemConfigTransactor creates a new write-only instance of SystemConfig, bound to a specific deployed contract. +func NewSystemConfigTransactor(address common.Address, transactor bind.ContractTransactor) (*SystemConfigTransactor, error) { + contract, err := bindSystemConfig(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &SystemConfigTransactor{contract: contract}, nil +} + +// NewSystemConfigFilterer creates a new log filterer instance of SystemConfig, bound to a specific deployed contract. +func NewSystemConfigFilterer(address common.Address, filterer bind.ContractFilterer) (*SystemConfigFilterer, error) { + contract, err := bindSystemConfig(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &SystemConfigFilterer{contract: contract}, nil +} + +// bindSystemConfig binds a generic wrapper to an already deployed contract. +func bindSystemConfig(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := SystemConfigMetaData.GetAbi() + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SystemConfig *SystemConfigRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SystemConfig.Contract.SystemConfigCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SystemConfig *SystemConfigRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SystemConfig.Contract.SystemConfigTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SystemConfig *SystemConfigRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SystemConfig.Contract.SystemConfigTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_SystemConfig *SystemConfigCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _SystemConfig.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_SystemConfig *SystemConfigTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SystemConfig.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_SystemConfig *SystemConfigTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _SystemConfig.Contract.contract.Transact(opts, method, params...) +} + +// BATCHINBOXSLOT is a free data retrieval call binding the contract method 0xbc49ce5f. +// +// Solidity: function BATCH_INBOX_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) BATCHINBOXSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "BATCH_INBOX_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// BATCHINBOXSLOT is a free data retrieval call binding the contract method 0xbc49ce5f. +// +// Solidity: function BATCH_INBOX_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) BATCHINBOXSLOT() ([32]byte, error) { + return _SystemConfig.Contract.BATCHINBOXSLOT(&_SystemConfig.CallOpts) +} + +// BATCHINBOXSLOT is a free data retrieval call binding the contract method 0xbc49ce5f. +// +// Solidity: function BATCH_INBOX_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) BATCHINBOXSLOT() ([32]byte, error) { + return _SystemConfig.Contract.BATCHINBOXSLOT(&_SystemConfig.CallOpts) +} + +// DELAYEDWETHSLOT is a free data retrieval call binding the contract method 0x7e54b8ad. +// +// Solidity: function DELAYED_WETH_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) DELAYEDWETHSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "DELAYED_WETH_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// DELAYEDWETHSLOT is a free data retrieval call binding the contract method 0x7e54b8ad. +// +// Solidity: function DELAYED_WETH_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) DELAYEDWETHSLOT() ([32]byte, error) { + return _SystemConfig.Contract.DELAYEDWETHSLOT(&_SystemConfig.CallOpts) +} + +// DELAYEDWETHSLOT is a free data retrieval call binding the contract method 0x7e54b8ad. +// +// Solidity: function DELAYED_WETH_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) DELAYEDWETHSLOT() ([32]byte, error) { + return _SystemConfig.Contract.DELAYEDWETHSLOT(&_SystemConfig.CallOpts) +} + +// L1CROSSDOMAINMESSENGERSLOT is a free data retrieval call binding the contract method 0x5d73369c. +// +// Solidity: function L1_CROSS_DOMAIN_MESSENGER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) L1CROSSDOMAINMESSENGERSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "L1_CROSS_DOMAIN_MESSENGER_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// L1CROSSDOMAINMESSENGERSLOT is a free data retrieval call binding the contract method 0x5d73369c. +// +// Solidity: function L1_CROSS_DOMAIN_MESSENGER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) L1CROSSDOMAINMESSENGERSLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1CROSSDOMAINMESSENGERSLOT(&_SystemConfig.CallOpts) +} + +// L1CROSSDOMAINMESSENGERSLOT is a free data retrieval call binding the contract method 0x5d73369c. +// +// Solidity: function L1_CROSS_DOMAIN_MESSENGER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) L1CROSSDOMAINMESSENGERSLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1CROSSDOMAINMESSENGERSLOT(&_SystemConfig.CallOpts) +} + +// L1ERC721BRIDGESLOT is a free data retrieval call binding the contract method 0x19f5cea8. +// +// Solidity: function L1_ERC_721_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) L1ERC721BRIDGESLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "L1_ERC_721_BRIDGE_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// L1ERC721BRIDGESLOT is a free data retrieval call binding the contract method 0x19f5cea8. +// +// Solidity: function L1_ERC_721_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) L1ERC721BRIDGESLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1ERC721BRIDGESLOT(&_SystemConfig.CallOpts) +} + +// L1ERC721BRIDGESLOT is a free data retrieval call binding the contract method 0x19f5cea8. +// +// Solidity: function L1_ERC_721_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) L1ERC721BRIDGESLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1ERC721BRIDGESLOT(&_SystemConfig.CallOpts) +} + +// L1STANDARDBRIDGESLOT is a free data retrieval call binding the contract method 0xf8c68de0. +// +// Solidity: function L1_STANDARD_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) L1STANDARDBRIDGESLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "L1_STANDARD_BRIDGE_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// L1STANDARDBRIDGESLOT is a free data retrieval call binding the contract method 0xf8c68de0. +// +// Solidity: function L1_STANDARD_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) L1STANDARDBRIDGESLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1STANDARDBRIDGESLOT(&_SystemConfig.CallOpts) +} + +// L1STANDARDBRIDGESLOT is a free data retrieval call binding the contract method 0xf8c68de0. +// +// Solidity: function L1_STANDARD_BRIDGE_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) L1STANDARDBRIDGESLOT() ([32]byte, error) { + return _SystemConfig.Contract.L1STANDARDBRIDGESLOT(&_SystemConfig.CallOpts) +} + +// OPCMSLOT is a free data retrieval call binding the contract method 0x2efe2c63. +// +// Solidity: function OPCM_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) OPCMSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "OPCM_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// OPCMSLOT is a free data retrieval call binding the contract method 0x2efe2c63. +// +// Solidity: function OPCM_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) OPCMSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPCMSLOT(&_SystemConfig.CallOpts) +} + +// OPCMSLOT is a free data retrieval call binding the contract method 0x2efe2c63. +// +// Solidity: function OPCM_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) OPCMSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPCMSLOT(&_SystemConfig.CallOpts) +} + +// OPTIMISMMINTABLEERC20FACTORYSLOT is a free data retrieval call binding the contract method 0x06c92657. +// +// Solidity: function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) OPTIMISMMINTABLEERC20FACTORYSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// OPTIMISMMINTABLEERC20FACTORYSLOT is a free data retrieval call binding the contract method 0x06c92657. +// +// Solidity: function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) OPTIMISMMINTABLEERC20FACTORYSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPTIMISMMINTABLEERC20FACTORYSLOT(&_SystemConfig.CallOpts) +} + +// OPTIMISMMINTABLEERC20FACTORYSLOT is a free data retrieval call binding the contract method 0x06c92657. +// +// Solidity: function OPTIMISM_MINTABLE_ERC20_FACTORY_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) OPTIMISMMINTABLEERC20FACTORYSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPTIMISMMINTABLEERC20FACTORYSLOT(&_SystemConfig.CallOpts) +} + +// OPTIMISMPORTALSLOT is a free data retrieval call binding the contract method 0xfd32aa0f. +// +// Solidity: function OPTIMISM_PORTAL_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) OPTIMISMPORTALSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "OPTIMISM_PORTAL_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// OPTIMISMPORTALSLOT is a free data retrieval call binding the contract method 0xfd32aa0f. +// +// Solidity: function OPTIMISM_PORTAL_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) OPTIMISMPORTALSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPTIMISMPORTALSLOT(&_SystemConfig.CallOpts) +} + +// OPTIMISMPORTALSLOT is a free data retrieval call binding the contract method 0xfd32aa0f. +// +// Solidity: function OPTIMISM_PORTAL_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) OPTIMISMPORTALSLOT() ([32]byte, error) { + return _SystemConfig.Contract.OPTIMISMPORTALSLOT(&_SystemConfig.CallOpts) +} + +// STARTBLOCKSLOT is a free data retrieval call binding the contract method 0xe0e2016d. +// +// Solidity: function START_BLOCK_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) STARTBLOCKSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "START_BLOCK_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// STARTBLOCKSLOT is a free data retrieval call binding the contract method 0xe0e2016d. +// +// Solidity: function START_BLOCK_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) STARTBLOCKSLOT() ([32]byte, error) { + return _SystemConfig.Contract.STARTBLOCKSLOT(&_SystemConfig.CallOpts) +} + +// STARTBLOCKSLOT is a free data retrieval call binding the contract method 0xe0e2016d. +// +// Solidity: function START_BLOCK_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) STARTBLOCKSLOT() ([32]byte, error) { + return _SystemConfig.Contract.STARTBLOCKSLOT(&_SystemConfig.CallOpts) +} + +// UNSAFEBLOCKSIGNERSLOT is a free data retrieval call binding the contract method 0x4f16540b. +// +// Solidity: function UNSAFE_BLOCK_SIGNER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) UNSAFEBLOCKSIGNERSLOT(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "UNSAFE_BLOCK_SIGNER_SLOT") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// UNSAFEBLOCKSIGNERSLOT is a free data retrieval call binding the contract method 0x4f16540b. +// +// Solidity: function UNSAFE_BLOCK_SIGNER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) UNSAFEBLOCKSIGNERSLOT() ([32]byte, error) { + return _SystemConfig.Contract.UNSAFEBLOCKSIGNERSLOT(&_SystemConfig.CallOpts) +} + +// UNSAFEBLOCKSIGNERSLOT is a free data retrieval call binding the contract method 0x4f16540b. +// +// Solidity: function UNSAFE_BLOCK_SIGNER_SLOT() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) UNSAFEBLOCKSIGNERSLOT() ([32]byte, error) { + return _SystemConfig.Contract.UNSAFEBLOCKSIGNERSLOT(&_SystemConfig.CallOpts) +} + +// VERSION is a free data retrieval call binding the contract method 0xffa1ad74. +// +// Solidity: function VERSION() view returns(uint256) +func (_SystemConfig *SystemConfigCaller) VERSION(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "VERSION") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// VERSION is a free data retrieval call binding the contract method 0xffa1ad74. +// +// Solidity: function VERSION() view returns(uint256) +func (_SystemConfig *SystemConfigSession) VERSION() (*big.Int, error) { + return _SystemConfig.Contract.VERSION(&_SystemConfig.CallOpts) +} + +// VERSION is a free data retrieval call binding the contract method 0xffa1ad74. +// +// Solidity: function VERSION() view returns(uint256) +func (_SystemConfig *SystemConfigCallerSession) VERSION() (*big.Int, error) { + return _SystemConfig.Contract.VERSION(&_SystemConfig.CallOpts) +} + +// BasefeeScalar is a free data retrieval call binding the contract method 0xbfb14fb7. +// +// Solidity: function basefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) BasefeeScalar(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "basefeeScalar") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// BasefeeScalar is a free data retrieval call binding the contract method 0xbfb14fb7. +// +// Solidity: function basefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigSession) BasefeeScalar() (uint32, error) { + return _SystemConfig.Contract.BasefeeScalar(&_SystemConfig.CallOpts) +} + +// BasefeeScalar is a free data retrieval call binding the contract method 0xbfb14fb7. +// +// Solidity: function basefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) BasefeeScalar() (uint32, error) { + return _SystemConfig.Contract.BasefeeScalar(&_SystemConfig.CallOpts) +} + +// BatchInbox is a free data retrieval call binding the contract method 0xdac6e63a. +// +// Solidity: function batchInbox() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) BatchInbox(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "batchInbox") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// BatchInbox is a free data retrieval call binding the contract method 0xdac6e63a. +// +// Solidity: function batchInbox() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) BatchInbox() (common.Address, error) { + return _SystemConfig.Contract.BatchInbox(&_SystemConfig.CallOpts) +} + +// BatchInbox is a free data retrieval call binding the contract method 0xdac6e63a. +// +// Solidity: function batchInbox() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) BatchInbox() (common.Address, error) { + return _SystemConfig.Contract.BatchInbox(&_SystemConfig.CallOpts) +} + +// BatcherHash is a free data retrieval call binding the contract method 0xe81b2c6d. +// +// Solidity: function batcherHash() view returns(bytes32) +func (_SystemConfig *SystemConfigCaller) BatcherHash(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "batcherHash") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// BatcherHash is a free data retrieval call binding the contract method 0xe81b2c6d. +// +// Solidity: function batcherHash() view returns(bytes32) +func (_SystemConfig *SystemConfigSession) BatcherHash() ([32]byte, error) { + return _SystemConfig.Contract.BatcherHash(&_SystemConfig.CallOpts) +} + +// BatcherHash is a free data retrieval call binding the contract method 0xe81b2c6d. +// +// Solidity: function batcherHash() view returns(bytes32) +func (_SystemConfig *SystemConfigCallerSession) BatcherHash() ([32]byte, error) { + return _SystemConfig.Contract.BatcherHash(&_SystemConfig.CallOpts) +} + +// BlobbasefeeScalar is a free data retrieval call binding the contract method 0xec707517. +// +// Solidity: function blobbasefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) BlobbasefeeScalar(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "blobbasefeeScalar") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// BlobbasefeeScalar is a free data retrieval call binding the contract method 0xec707517. +// +// Solidity: function blobbasefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigSession) BlobbasefeeScalar() (uint32, error) { + return _SystemConfig.Contract.BlobbasefeeScalar(&_SystemConfig.CallOpts) +} + +// BlobbasefeeScalar is a free data retrieval call binding the contract method 0xec707517. +// +// Solidity: function blobbasefeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) BlobbasefeeScalar() (uint32, error) { + return _SystemConfig.Contract.BlobbasefeeScalar(&_SystemConfig.CallOpts) +} + +// DaFootprintGasScalar is a free data retrieval call binding the contract method 0xfe3d5710. +// +// Solidity: function daFootprintGasScalar() view returns(uint16) +func (_SystemConfig *SystemConfigCaller) DaFootprintGasScalar(opts *bind.CallOpts) (uint16, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "daFootprintGasScalar") + + if err != nil { + return *new(uint16), err + } + + out0 := *abi.ConvertType(out[0], new(uint16)).(*uint16) + + return out0, err + +} + +// DaFootprintGasScalar is a free data retrieval call binding the contract method 0xfe3d5710. +// +// Solidity: function daFootprintGasScalar() view returns(uint16) +func (_SystemConfig *SystemConfigSession) DaFootprintGasScalar() (uint16, error) { + return _SystemConfig.Contract.DaFootprintGasScalar(&_SystemConfig.CallOpts) +} + +// DaFootprintGasScalar is a free data retrieval call binding the contract method 0xfe3d5710. +// +// Solidity: function daFootprintGasScalar() view returns(uint16) +func (_SystemConfig *SystemConfigCallerSession) DaFootprintGasScalar() (uint16, error) { + return _SystemConfig.Contract.DaFootprintGasScalar(&_SystemConfig.CallOpts) +} + +// DelayedWETH is a free data retrieval call binding the contract method 0x215b7a1c. +// +// Solidity: function delayedWETH() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) DelayedWETH(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "delayedWETH") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// DelayedWETH is a free data retrieval call binding the contract method 0x215b7a1c. +// +// Solidity: function delayedWETH() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) DelayedWETH() (common.Address, error) { + return _SystemConfig.Contract.DelayedWETH(&_SystemConfig.CallOpts) +} + +// DelayedWETH is a free data retrieval call binding the contract method 0x215b7a1c. +// +// Solidity: function delayedWETH() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) DelayedWETH() (common.Address, error) { + return _SystemConfig.Contract.DelayedWETH(&_SystemConfig.CallOpts) +} + +// DisputeGameFactory is a free data retrieval call binding the contract method 0xf2b4e617. +// +// Solidity: function disputeGameFactory() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) DisputeGameFactory(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "disputeGameFactory") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// DisputeGameFactory is a free data retrieval call binding the contract method 0xf2b4e617. +// +// Solidity: function disputeGameFactory() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) DisputeGameFactory() (common.Address, error) { + return _SystemConfig.Contract.DisputeGameFactory(&_SystemConfig.CallOpts) +} + +// DisputeGameFactory is a free data retrieval call binding the contract method 0xf2b4e617. +// +// Solidity: function disputeGameFactory() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) DisputeGameFactory() (common.Address, error) { + return _SystemConfig.Contract.DisputeGameFactory(&_SystemConfig.CallOpts) +} + +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) Eip1559Denominator(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "eip1559Denominator") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigSession) Eip1559Denominator() (uint32, error) { + return _SystemConfig.Contract.Eip1559Denominator(&_SystemConfig.CallOpts) +} + +// Eip1559Denominator is a free data retrieval call binding the contract method 0xd220a9e0. +// +// Solidity: function eip1559Denominator() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) Eip1559Denominator() (uint32, error) { + return _SystemConfig.Contract.Eip1559Denominator(&_SystemConfig.CallOpts) +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) Eip1559Elasticity(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "eip1559Elasticity") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigSession) Eip1559Elasticity() (uint32, error) { + return _SystemConfig.Contract.Eip1559Elasticity(&_SystemConfig.CallOpts) +} + +// Eip1559Elasticity is a free data retrieval call binding the contract method 0xc9ff2d16. +// +// Solidity: function eip1559Elasticity() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) Eip1559Elasticity() (uint32, error) { + return _SystemConfig.Contract.Eip1559Elasticity(&_SystemConfig.CallOpts) +} + +// GasLimit is a free data retrieval call binding the contract method 0xf68016b7. +// +// Solidity: function gasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigCaller) GasLimit(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "gasLimit") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// GasLimit is a free data retrieval call binding the contract method 0xf68016b7. +// +// Solidity: function gasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigSession) GasLimit() (uint64, error) { + return _SystemConfig.Contract.GasLimit(&_SystemConfig.CallOpts) +} + +// GasLimit is a free data retrieval call binding the contract method 0xf68016b7. +// +// Solidity: function gasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) GasLimit() (uint64, error) { + return _SystemConfig.Contract.GasLimit(&_SystemConfig.CallOpts) +} + +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. +// +// Solidity: function getAddresses() view returns((address,address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigCaller) GetAddresses(opts *bind.CallOpts) (SystemConfigAddresses, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "getAddresses") + + if err != nil { + return *new(SystemConfigAddresses), err + } + + out0 := *abi.ConvertType(out[0], new(SystemConfigAddresses)).(*SystemConfigAddresses) + + return out0, err + +} + +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. +// +// Solidity: function getAddresses() view returns((address,address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigSession) GetAddresses() (SystemConfigAddresses, error) { + return _SystemConfig.Contract.GetAddresses(&_SystemConfig.CallOpts) +} + +// GetAddresses is a free data retrieval call binding the contract method 0xa39fac12. +// +// Solidity: function getAddresses() view returns((address,address,address,address,address,address,address)) +func (_SystemConfig *SystemConfigCallerSession) GetAddresses() (SystemConfigAddresses, error) { + return _SystemConfig.Contract.GetAddresses(&_SystemConfig.CallOpts) +} + +// Guardian is a free data retrieval call binding the contract method 0x452a9320. +// +// Solidity: function guardian() view returns(address) +func (_SystemConfig *SystemConfigCaller) Guardian(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "guardian") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Guardian is a free data retrieval call binding the contract method 0x452a9320. +// +// Solidity: function guardian() view returns(address) +func (_SystemConfig *SystemConfigSession) Guardian() (common.Address, error) { + return _SystemConfig.Contract.Guardian(&_SystemConfig.CallOpts) +} + +// Guardian is a free data retrieval call binding the contract method 0x452a9320. +// +// Solidity: function guardian() view returns(address) +func (_SystemConfig *SystemConfigCallerSession) Guardian() (common.Address, error) { + return _SystemConfig.Contract.Guardian(&_SystemConfig.CallOpts) +} + +// InitVersion is a free data retrieval call binding the contract method 0x38d38c97. +// +// Solidity: function initVersion() view returns(uint8) +func (_SystemConfig *SystemConfigCaller) InitVersion(opts *bind.CallOpts) (uint8, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "initVersion") + + if err != nil { + return *new(uint8), err + } + + out0 := *abi.ConvertType(out[0], new(uint8)).(*uint8) + + return out0, err + +} + +// InitVersion is a free data retrieval call binding the contract method 0x38d38c97. +// +// Solidity: function initVersion() view returns(uint8) +func (_SystemConfig *SystemConfigSession) InitVersion() (uint8, error) { + return _SystemConfig.Contract.InitVersion(&_SystemConfig.CallOpts) +} + +// InitVersion is a free data retrieval call binding the contract method 0x38d38c97. +// +// Solidity: function initVersion() view returns(uint8) +func (_SystemConfig *SystemConfigCallerSession) InitVersion() (uint8, error) { + return _SystemConfig.Contract.InitVersion(&_SystemConfig.CallOpts) +} + +// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. +// +// Solidity: function isCustomGasToken() view returns(bool) +func (_SystemConfig *SystemConfigCaller) IsCustomGasToken(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "isCustomGasToken") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. +// +// Solidity: function isCustomGasToken() view returns(bool) +func (_SystemConfig *SystemConfigSession) IsCustomGasToken() (bool, error) { + return _SystemConfig.Contract.IsCustomGasToken(&_SystemConfig.CallOpts) +} + +// IsCustomGasToken is a free data retrieval call binding the contract method 0x21326849. +// +// Solidity: function isCustomGasToken() view returns(bool) +func (_SystemConfig *SystemConfigCallerSession) IsCustomGasToken() (bool, error) { + return _SystemConfig.Contract.IsCustomGasToken(&_SystemConfig.CallOpts) +} + +// IsFeatureEnabled is a free data retrieval call binding the contract method 0x47af267b. +// +// Solidity: function isFeatureEnabled(bytes32 ) view returns(bool) +func (_SystemConfig *SystemConfigCaller) IsFeatureEnabled(opts *bind.CallOpts, arg0 [32]byte) (bool, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "isFeatureEnabled", arg0) + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// IsFeatureEnabled is a free data retrieval call binding the contract method 0x47af267b. +// +// Solidity: function isFeatureEnabled(bytes32 ) view returns(bool) +func (_SystemConfig *SystemConfigSession) IsFeatureEnabled(arg0 [32]byte) (bool, error) { + return _SystemConfig.Contract.IsFeatureEnabled(&_SystemConfig.CallOpts, arg0) +} + +// IsFeatureEnabled is a free data retrieval call binding the contract method 0x47af267b. +// +// Solidity: function isFeatureEnabled(bytes32 ) view returns(bool) +func (_SystemConfig *SystemConfigCallerSession) IsFeatureEnabled(arg0 [32]byte) (bool, error) { + return _SystemConfig.Contract.IsFeatureEnabled(&_SystemConfig.CallOpts, arg0) +} + +// L1CrossDomainMessenger is a free data retrieval call binding the contract method 0xa7119869. +// +// Solidity: function l1CrossDomainMessenger() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) L1CrossDomainMessenger(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "l1CrossDomainMessenger") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// L1CrossDomainMessenger is a free data retrieval call binding the contract method 0xa7119869. +// +// Solidity: function l1CrossDomainMessenger() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) L1CrossDomainMessenger() (common.Address, error) { + return _SystemConfig.Contract.L1CrossDomainMessenger(&_SystemConfig.CallOpts) +} + +// L1CrossDomainMessenger is a free data retrieval call binding the contract method 0xa7119869. +// +// Solidity: function l1CrossDomainMessenger() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) L1CrossDomainMessenger() (common.Address, error) { + return _SystemConfig.Contract.L1CrossDomainMessenger(&_SystemConfig.CallOpts) +} + +// L1ERC721Bridge is a free data retrieval call binding the contract method 0xc4e8ddfa. +// +// Solidity: function l1ERC721Bridge() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) L1ERC721Bridge(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "l1ERC721Bridge") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// L1ERC721Bridge is a free data retrieval call binding the contract method 0xc4e8ddfa. +// +// Solidity: function l1ERC721Bridge() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) L1ERC721Bridge() (common.Address, error) { + return _SystemConfig.Contract.L1ERC721Bridge(&_SystemConfig.CallOpts) +} + +// L1ERC721Bridge is a free data retrieval call binding the contract method 0xc4e8ddfa. +// +// Solidity: function l1ERC721Bridge() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) L1ERC721Bridge() (common.Address, error) { + return _SystemConfig.Contract.L1ERC721Bridge(&_SystemConfig.CallOpts) +} + +// L1StandardBridge is a free data retrieval call binding the contract method 0x078f29cf. +// +// Solidity: function l1StandardBridge() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) L1StandardBridge(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "l1StandardBridge") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// L1StandardBridge is a free data retrieval call binding the contract method 0x078f29cf. +// +// Solidity: function l1StandardBridge() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) L1StandardBridge() (common.Address, error) { + return _SystemConfig.Contract.L1StandardBridge(&_SystemConfig.CallOpts) +} + +// L1StandardBridge is a free data retrieval call binding the contract method 0x078f29cf. +// +// Solidity: function l1StandardBridge() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) L1StandardBridge() (common.Address, error) { + return _SystemConfig.Contract.L1StandardBridge(&_SystemConfig.CallOpts) +} + +// L2ChainId is a free data retrieval call binding the contract method 0xd6ae3cd5. +// +// Solidity: function l2ChainId() view returns(uint256) +func (_SystemConfig *SystemConfigCaller) L2ChainId(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "l2ChainId") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// L2ChainId is a free data retrieval call binding the contract method 0xd6ae3cd5. +// +// Solidity: function l2ChainId() view returns(uint256) +func (_SystemConfig *SystemConfigSession) L2ChainId() (*big.Int, error) { + return _SystemConfig.Contract.L2ChainId(&_SystemConfig.CallOpts) +} + +// L2ChainId is a free data retrieval call binding the contract method 0xd6ae3cd5. +// +// Solidity: function l2ChainId() view returns(uint256) +func (_SystemConfig *SystemConfigCallerSession) L2ChainId() (*big.Int, error) { + return _SystemConfig.Contract.L2ChainId(&_SystemConfig.CallOpts) +} + +// LastUsedOPCM is a free data retrieval call binding the contract method 0x8ed95502. +// +// Solidity: function lastUsedOPCM() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) LastUsedOPCM(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "lastUsedOPCM") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// LastUsedOPCM is a free data retrieval call binding the contract method 0x8ed95502. +// +// Solidity: function lastUsedOPCM() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) LastUsedOPCM() (common.Address, error) { + return _SystemConfig.Contract.LastUsedOPCM(&_SystemConfig.CallOpts) +} + +// LastUsedOPCM is a free data retrieval call binding the contract method 0x8ed95502. +// +// Solidity: function lastUsedOPCM() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) LastUsedOPCM() (common.Address, error) { + return _SystemConfig.Contract.LastUsedOPCM(&_SystemConfig.CallOpts) +} + +// LastUsedOPCMVersion is a free data retrieval call binding the contract method 0x9fabcc84. +// +// Solidity: function lastUsedOPCMVersion() view returns(string version_) +func (_SystemConfig *SystemConfigCaller) LastUsedOPCMVersion(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "lastUsedOPCMVersion") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// LastUsedOPCMVersion is a free data retrieval call binding the contract method 0x9fabcc84. +// +// Solidity: function lastUsedOPCMVersion() view returns(string version_) +func (_SystemConfig *SystemConfigSession) LastUsedOPCMVersion() (string, error) { + return _SystemConfig.Contract.LastUsedOPCMVersion(&_SystemConfig.CallOpts) +} + +// LastUsedOPCMVersion is a free data retrieval call binding the contract method 0x9fabcc84. +// +// Solidity: function lastUsedOPCMVersion() view returns(string version_) +func (_SystemConfig *SystemConfigCallerSession) LastUsedOPCMVersion() (string, error) { + return _SystemConfig.Contract.LastUsedOPCMVersion(&_SystemConfig.CallOpts) +} + +// MaximumGasLimit is a free data retrieval call binding the contract method 0x0ae14b1b. +// +// Solidity: function maximumGasLimit() pure returns(uint64) +func (_SystemConfig *SystemConfigCaller) MaximumGasLimit(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "maximumGasLimit") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// MaximumGasLimit is a free data retrieval call binding the contract method 0x0ae14b1b. +// +// Solidity: function maximumGasLimit() pure returns(uint64) +func (_SystemConfig *SystemConfigSession) MaximumGasLimit() (uint64, error) { + return _SystemConfig.Contract.MaximumGasLimit(&_SystemConfig.CallOpts) +} + +// MaximumGasLimit is a free data retrieval call binding the contract method 0x0ae14b1b. +// +// Solidity: function maximumGasLimit() pure returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) MaximumGasLimit() (uint64, error) { + return _SystemConfig.Contract.MaximumGasLimit(&_SystemConfig.CallOpts) +} + +// MinBaseFee is a free data retrieval call binding the contract method 0xa62611a2. +// +// Solidity: function minBaseFee() view returns(uint64) +func (_SystemConfig *SystemConfigCaller) MinBaseFee(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "minBaseFee") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// MinBaseFee is a free data retrieval call binding the contract method 0xa62611a2. +// +// Solidity: function minBaseFee() view returns(uint64) +func (_SystemConfig *SystemConfigSession) MinBaseFee() (uint64, error) { + return _SystemConfig.Contract.MinBaseFee(&_SystemConfig.CallOpts) +} + +// MinBaseFee is a free data retrieval call binding the contract method 0xa62611a2. +// +// Solidity: function minBaseFee() view returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) MinBaseFee() (uint64, error) { + return _SystemConfig.Contract.MinBaseFee(&_SystemConfig.CallOpts) +} + +// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d. +// +// Solidity: function minimumGasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigCaller) MinimumGasLimit(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "minimumGasLimit") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d. +// +// Solidity: function minimumGasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigSession) MinimumGasLimit() (uint64, error) { + return _SystemConfig.Contract.MinimumGasLimit(&_SystemConfig.CallOpts) +} + +// MinimumGasLimit is a free data retrieval call binding the contract method 0x4add321d. +// +// Solidity: function minimumGasLimit() view returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) MinimumGasLimit() (uint64, error) { + return _SystemConfig.Contract.MinimumGasLimit(&_SystemConfig.CallOpts) +} + +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigCaller) OperatorFeeConstant(opts *bind.CallOpts) (uint64, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "operatorFeeConstant") + + if err != nil { + return *new(uint64), err + } + + out0 := *abi.ConvertType(out[0], new(uint64)).(*uint64) + + return out0, err + +} + +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigSession) OperatorFeeConstant() (uint64, error) { + return _SystemConfig.Contract.OperatorFeeConstant(&_SystemConfig.CallOpts) +} + +// OperatorFeeConstant is a free data retrieval call binding the contract method 0x16d3bc7f. +// +// Solidity: function operatorFeeConstant() view returns(uint64) +func (_SystemConfig *SystemConfigCallerSession) OperatorFeeConstant() (uint64, error) { + return _SystemConfig.Contract.OperatorFeeConstant(&_SystemConfig.CallOpts) +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCaller) OperatorFeeScalar(opts *bind.CallOpts) (uint32, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "operatorFeeScalar") + + if err != nil { + return *new(uint32), err + } + + out0 := *abi.ConvertType(out[0], new(uint32)).(*uint32) + + return out0, err + +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigSession) OperatorFeeScalar() (uint32, error) { + return _SystemConfig.Contract.OperatorFeeScalar(&_SystemConfig.CallOpts) +} + +// OperatorFeeScalar is a free data retrieval call binding the contract method 0x4d5d9a2a. +// +// Solidity: function operatorFeeScalar() view returns(uint32) +func (_SystemConfig *SystemConfigCallerSession) OperatorFeeScalar() (uint32, error) { + return _SystemConfig.Contract.OperatorFeeScalar(&_SystemConfig.CallOpts) +} + +// OptimismMintableERC20Factory is a free data retrieval call binding the contract method 0x9b7d7f0a. +// +// Solidity: function optimismMintableERC20Factory() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) OptimismMintableERC20Factory(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "optimismMintableERC20Factory") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// OptimismMintableERC20Factory is a free data retrieval call binding the contract method 0x9b7d7f0a. +// +// Solidity: function optimismMintableERC20Factory() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) OptimismMintableERC20Factory() (common.Address, error) { + return _SystemConfig.Contract.OptimismMintableERC20Factory(&_SystemConfig.CallOpts) +} + +// OptimismMintableERC20Factory is a free data retrieval call binding the contract method 0x9b7d7f0a. +// +// Solidity: function optimismMintableERC20Factory() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) OptimismMintableERC20Factory() (common.Address, error) { + return _SystemConfig.Contract.OptimismMintableERC20Factory(&_SystemConfig.CallOpts) +} + +// OptimismPortal is a free data retrieval call binding the contract method 0x0a49cb03. +// +// Solidity: function optimismPortal() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) OptimismPortal(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "optimismPortal") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// OptimismPortal is a free data retrieval call binding the contract method 0x0a49cb03. +// +// Solidity: function optimismPortal() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) OptimismPortal() (common.Address, error) { + return _SystemConfig.Contract.OptimismPortal(&_SystemConfig.CallOpts) +} + +// OptimismPortal is a free data retrieval call binding the contract method 0x0a49cb03. +// +// Solidity: function optimismPortal() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) OptimismPortal() (common.Address, error) { + return _SystemConfig.Contract.OptimismPortal(&_SystemConfig.CallOpts) +} + +// Overhead is a free data retrieval call binding the contract method 0x0c18c162. +// +// Solidity: function overhead() view returns(uint256) +func (_SystemConfig *SystemConfigCaller) Overhead(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "overhead") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Overhead is a free data retrieval call binding the contract method 0x0c18c162. +// +// Solidity: function overhead() view returns(uint256) +func (_SystemConfig *SystemConfigSession) Overhead() (*big.Int, error) { + return _SystemConfig.Contract.Overhead(&_SystemConfig.CallOpts) +} + +// Overhead is a free data retrieval call binding the contract method 0x0c18c162. +// +// Solidity: function overhead() view returns(uint256) +func (_SystemConfig *SystemConfigCallerSession) Overhead() (*big.Int, error) { + return _SystemConfig.Contract.Overhead(&_SystemConfig.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SystemConfig *SystemConfigCaller) Owner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "owner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SystemConfig *SystemConfigSession) Owner() (common.Address, error) { + return _SystemConfig.Contract.Owner(&_SystemConfig.CallOpts) +} + +// Owner is a free data retrieval call binding the contract method 0x8da5cb5b. +// +// Solidity: function owner() view returns(address) +func (_SystemConfig *SystemConfigCallerSession) Owner() (common.Address, error) { + return _SystemConfig.Contract.Owner(&_SystemConfig.CallOpts) +} + +// Paused is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(bool) +func (_SystemConfig *SystemConfigCaller) Paused(opts *bind.CallOpts) (bool, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "paused") + + if err != nil { + return *new(bool), err + } + + out0 := *abi.ConvertType(out[0], new(bool)).(*bool) + + return out0, err + +} + +// Paused is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(bool) +func (_SystemConfig *SystemConfigSession) Paused() (bool, error) { + return _SystemConfig.Contract.Paused(&_SystemConfig.CallOpts) +} + +// Paused is a free data retrieval call binding the contract method 0x5c975abb. +// +// Solidity: function paused() view returns(bool) +func (_SystemConfig *SystemConfigCallerSession) Paused() (bool, error) { + return _SystemConfig.Contract.Paused(&_SystemConfig.CallOpts) +} + +// ProxyAdmin is a free data retrieval call binding the contract method 0x3e47158c. +// +// Solidity: function proxyAdmin() view returns(address) +func (_SystemConfig *SystemConfigCaller) ProxyAdmin(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "proxyAdmin") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ProxyAdmin is a free data retrieval call binding the contract method 0x3e47158c. +// +// Solidity: function proxyAdmin() view returns(address) +func (_SystemConfig *SystemConfigSession) ProxyAdmin() (common.Address, error) { + return _SystemConfig.Contract.ProxyAdmin(&_SystemConfig.CallOpts) +} + +// ProxyAdmin is a free data retrieval call binding the contract method 0x3e47158c. +// +// Solidity: function proxyAdmin() view returns(address) +func (_SystemConfig *SystemConfigCallerSession) ProxyAdmin() (common.Address, error) { + return _SystemConfig.Contract.ProxyAdmin(&_SystemConfig.CallOpts) +} + +// ProxyAdminOwner is a free data retrieval call binding the contract method 0xdad544e0. +// +// Solidity: function proxyAdminOwner() view returns(address) +func (_SystemConfig *SystemConfigCaller) ProxyAdminOwner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "proxyAdminOwner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// ProxyAdminOwner is a free data retrieval call binding the contract method 0xdad544e0. +// +// Solidity: function proxyAdminOwner() view returns(address) +func (_SystemConfig *SystemConfigSession) ProxyAdminOwner() (common.Address, error) { + return _SystemConfig.Contract.ProxyAdminOwner(&_SystemConfig.CallOpts) +} + +// ProxyAdminOwner is a free data retrieval call binding the contract method 0xdad544e0. +// +// Solidity: function proxyAdminOwner() view returns(address) +func (_SystemConfig *SystemConfigCallerSession) ProxyAdminOwner() (common.Address, error) { + return _SystemConfig.Contract.ProxyAdminOwner(&_SystemConfig.CallOpts) +} + +// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. +// +// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) +func (_SystemConfig *SystemConfigCaller) ResourceConfig(opts *bind.CallOpts) (IResourceMeteringResourceConfig, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "resourceConfig") + + if err != nil { + return *new(IResourceMeteringResourceConfig), err + } + + out0 := *abi.ConvertType(out[0], new(IResourceMeteringResourceConfig)).(*IResourceMeteringResourceConfig) + + return out0, err + +} + +// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. +// +// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) +func (_SystemConfig *SystemConfigSession) ResourceConfig() (IResourceMeteringResourceConfig, error) { + return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts) +} + +// ResourceConfig is a free data retrieval call binding the contract method 0xcc731b02. +// +// Solidity: function resourceConfig() view returns((uint32,uint8,uint8,uint32,uint32,uint128)) +func (_SystemConfig *SystemConfigCallerSession) ResourceConfig() (IResourceMeteringResourceConfig, error) { + return _SystemConfig.Contract.ResourceConfig(&_SystemConfig.CallOpts) +} + +// Scalar is a free data retrieval call binding the contract method 0xf45e65d8. +// +// Solidity: function scalar() view returns(uint256) +func (_SystemConfig *SystemConfigCaller) Scalar(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "scalar") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// Scalar is a free data retrieval call binding the contract method 0xf45e65d8. +// +// Solidity: function scalar() view returns(uint256) +func (_SystemConfig *SystemConfigSession) Scalar() (*big.Int, error) { + return _SystemConfig.Contract.Scalar(&_SystemConfig.CallOpts) +} + +// Scalar is a free data retrieval call binding the contract method 0xf45e65d8. +// +// Solidity: function scalar() view returns(uint256) +func (_SystemConfig *SystemConfigCallerSession) Scalar() (*big.Int, error) { + return _SystemConfig.Contract.Scalar(&_SystemConfig.CallOpts) +} + +// StartBlock is a free data retrieval call binding the contract method 0x48cd4cb1. +// +// Solidity: function startBlock() view returns(uint256 startBlock_) +func (_SystemConfig *SystemConfigCaller) StartBlock(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "startBlock") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// StartBlock is a free data retrieval call binding the contract method 0x48cd4cb1. +// +// Solidity: function startBlock() view returns(uint256 startBlock_) +func (_SystemConfig *SystemConfigSession) StartBlock() (*big.Int, error) { + return _SystemConfig.Contract.StartBlock(&_SystemConfig.CallOpts) +} + +// StartBlock is a free data retrieval call binding the contract method 0x48cd4cb1. +// +// Solidity: function startBlock() view returns(uint256 startBlock_) +func (_SystemConfig *SystemConfigCallerSession) StartBlock() (*big.Int, error) { + return _SystemConfig.Contract.StartBlock(&_SystemConfig.CallOpts) +} + +// SuperchainConfig is a free data retrieval call binding the contract method 0x35e80ab3. +// +// Solidity: function superchainConfig() view returns(address) +func (_SystemConfig *SystemConfigCaller) SuperchainConfig(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "superchainConfig") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// SuperchainConfig is a free data retrieval call binding the contract method 0x35e80ab3. +// +// Solidity: function superchainConfig() view returns(address) +func (_SystemConfig *SystemConfigSession) SuperchainConfig() (common.Address, error) { + return _SystemConfig.Contract.SuperchainConfig(&_SystemConfig.CallOpts) +} + +// SuperchainConfig is a free data retrieval call binding the contract method 0x35e80ab3. +// +// Solidity: function superchainConfig() view returns(address) +func (_SystemConfig *SystemConfigCallerSession) SuperchainConfig() (common.Address, error) { + return _SystemConfig.Contract.SuperchainConfig(&_SystemConfig.CallOpts) +} + +// UnsafeBlockSigner is a free data retrieval call binding the contract method 0x1fd19ee1. +// +// Solidity: function unsafeBlockSigner() view returns(address addr_) +func (_SystemConfig *SystemConfigCaller) UnsafeBlockSigner(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "unsafeBlockSigner") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// UnsafeBlockSigner is a free data retrieval call binding the contract method 0x1fd19ee1. +// +// Solidity: function unsafeBlockSigner() view returns(address addr_) +func (_SystemConfig *SystemConfigSession) UnsafeBlockSigner() (common.Address, error) { + return _SystemConfig.Contract.UnsafeBlockSigner(&_SystemConfig.CallOpts) +} + +// UnsafeBlockSigner is a free data retrieval call binding the contract method 0x1fd19ee1. +// +// Solidity: function unsafeBlockSigner() view returns(address addr_) +func (_SystemConfig *SystemConfigCallerSession) UnsafeBlockSigner() (common.Address, error) { + return _SystemConfig.Contract.UnsafeBlockSigner(&_SystemConfig.CallOpts) +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() pure returns(string) +func (_SystemConfig *SystemConfigCaller) Version(opts *bind.CallOpts) (string, error) { + var out []interface{} + err := _SystemConfig.contract.Call(opts, &out, "version") + + if err != nil { + return *new(string), err + } + + out0 := *abi.ConvertType(out[0], new(string)).(*string) + + return out0, err + +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() pure returns(string) +func (_SystemConfig *SystemConfigSession) Version() (string, error) { + return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) +} + +// Version is a free data retrieval call binding the contract method 0x54fd4d50. +// +// Solidity: function version() pure returns(string) +func (_SystemConfig *SystemConfigCallerSession) Version() (string, error) { + return _SystemConfig.Contract.Version(&_SystemConfig.CallOpts) +} + +// Initialize is a paid mutator transaction binding the contract method 0x2b598b8a. +// +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses, uint256 _l2ChainId, address _superchainConfig) returns() +func (_SystemConfig *SystemConfigTransactor) Initialize(opts *bind.TransactOpts, _owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses, _l2ChainId *big.Int, _superchainConfig common.Address) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "initialize", _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses, _l2ChainId, _superchainConfig) +} + +// Initialize is a paid mutator transaction binding the contract method 0x2b598b8a. +// +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses, uint256 _l2ChainId, address _superchainConfig) returns() +func (_SystemConfig *SystemConfigSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses, _l2ChainId *big.Int, _superchainConfig common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses, _l2ChainId, _superchainConfig) +} + +// Initialize is a paid mutator transaction binding the contract method 0x2b598b8a. +// +// Solidity: function initialize(address _owner, uint32 _basefeeScalar, uint32 _blobbasefeeScalar, bytes32 _batcherHash, uint64 _gasLimit, address _unsafeBlockSigner, (uint32,uint8,uint8,uint32,uint32,uint128) _config, address _batchInbox, (address,address,address,address,address,address,address) _addresses, uint256 _l2ChainId, address _superchainConfig) returns() +func (_SystemConfig *SystemConfigTransactorSession) Initialize(_owner common.Address, _basefeeScalar uint32, _blobbasefeeScalar uint32, _batcherHash [32]byte, _gasLimit uint64, _unsafeBlockSigner common.Address, _config IResourceMeteringResourceConfig, _batchInbox common.Address, _addresses SystemConfigAddresses, _l2ChainId *big.Int, _superchainConfig common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.Initialize(&_SystemConfig.TransactOpts, _owner, _basefeeScalar, _blobbasefeeScalar, _batcherHash, _gasLimit, _unsafeBlockSigner, _config, _batchInbox, _addresses, _l2ChainId, _superchainConfig) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SystemConfig *SystemConfigTransactor) RenounceOwnership(opts *bind.TransactOpts) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "renounceOwnership") +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SystemConfig *SystemConfigSession) RenounceOwnership() (*types.Transaction, error) { + return _SystemConfig.Contract.RenounceOwnership(&_SystemConfig.TransactOpts) +} + +// RenounceOwnership is a paid mutator transaction binding the contract method 0x715018a6. +// +// Solidity: function renounceOwnership() returns() +func (_SystemConfig *SystemConfigTransactorSession) RenounceOwnership() (*types.Transaction, error) { + return _SystemConfig.Contract.RenounceOwnership(&_SystemConfig.TransactOpts) +} + +// SetBatcherHash is a paid mutator transaction binding the contract method 0x0a2ca2a9. +// +// Solidity: function setBatcherHash(address _batcher) returns() +func (_SystemConfig *SystemConfigTransactor) SetBatcherHash(opts *bind.TransactOpts, _batcher common.Address) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setBatcherHash", _batcher) +} + +// SetBatcherHash is a paid mutator transaction binding the contract method 0x0a2ca2a9. +// +// Solidity: function setBatcherHash(address _batcher) returns() +func (_SystemConfig *SystemConfigSession) SetBatcherHash(_batcher common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.SetBatcherHash(&_SystemConfig.TransactOpts, _batcher) +} + +// SetBatcherHash is a paid mutator transaction binding the contract method 0x0a2ca2a9. +// +// Solidity: function setBatcherHash(address _batcher) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetBatcherHash(_batcher common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.SetBatcherHash(&_SystemConfig.TransactOpts, _batcher) +} + +// SetBatcherHash0 is a paid mutator transaction binding the contract method 0xc9b26f61. +// +// Solidity: function setBatcherHash(bytes32 _batcherHash) returns() +func (_SystemConfig *SystemConfigTransactor) SetBatcherHash0(opts *bind.TransactOpts, _batcherHash [32]byte) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setBatcherHash0", _batcherHash) +} + +// SetBatcherHash0 is a paid mutator transaction binding the contract method 0xc9b26f61. +// +// Solidity: function setBatcherHash(bytes32 _batcherHash) returns() +func (_SystemConfig *SystemConfigSession) SetBatcherHash0(_batcherHash [32]byte) (*types.Transaction, error) { + return _SystemConfig.Contract.SetBatcherHash0(&_SystemConfig.TransactOpts, _batcherHash) +} + +// SetBatcherHash0 is a paid mutator transaction binding the contract method 0xc9b26f61. +// +// Solidity: function setBatcherHash(bytes32 _batcherHash) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetBatcherHash0(_batcherHash [32]byte) (*types.Transaction, error) { + return _SystemConfig.Contract.SetBatcherHash0(&_SystemConfig.TransactOpts, _batcherHash) +} + +// SetDAFootprintGasScalar is a paid mutator transaction binding the contract method 0x20f06fdc. +// +// Solidity: function setDAFootprintGasScalar(uint16 _daFootprintGasScalar) returns() +func (_SystemConfig *SystemConfigTransactor) SetDAFootprintGasScalar(opts *bind.TransactOpts, _daFootprintGasScalar uint16) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setDAFootprintGasScalar", _daFootprintGasScalar) +} + +// SetDAFootprintGasScalar is a paid mutator transaction binding the contract method 0x20f06fdc. +// +// Solidity: function setDAFootprintGasScalar(uint16 _daFootprintGasScalar) returns() +func (_SystemConfig *SystemConfigSession) SetDAFootprintGasScalar(_daFootprintGasScalar uint16) (*types.Transaction, error) { + return _SystemConfig.Contract.SetDAFootprintGasScalar(&_SystemConfig.TransactOpts, _daFootprintGasScalar) +} + +// SetDAFootprintGasScalar is a paid mutator transaction binding the contract method 0x20f06fdc. +// +// Solidity: function setDAFootprintGasScalar(uint16 _daFootprintGasScalar) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetDAFootprintGasScalar(_daFootprintGasScalar uint16) (*types.Transaction, error) { + return _SystemConfig.Contract.SetDAFootprintGasScalar(&_SystemConfig.TransactOpts, _daFootprintGasScalar) +} + +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigTransactor) SetEIP1559Params(opts *bind.TransactOpts, _denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setEIP1559Params", _denominator, _elasticity) +} + +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigSession) SetEIP1559Params(_denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetEIP1559Params(&_SystemConfig.TransactOpts, _denominator, _elasticity) +} + +// SetEIP1559Params is a paid mutator transaction binding the contract method 0xc0fd4b41. +// +// Solidity: function setEIP1559Params(uint32 _denominator, uint32 _elasticity) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetEIP1559Params(_denominator uint32, _elasticity uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetEIP1559Params(&_SystemConfig.TransactOpts, _denominator, _elasticity) +} + +// SetFeature is a paid mutator transaction binding the contract method 0xf2c4bc9e. +// +// Solidity: function setFeature(bytes32 _feature, bool _enabled) returns() +func (_SystemConfig *SystemConfigTransactor) SetFeature(opts *bind.TransactOpts, _feature [32]byte, _enabled bool) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setFeature", _feature, _enabled) +} + +// SetFeature is a paid mutator transaction binding the contract method 0xf2c4bc9e. +// +// Solidity: function setFeature(bytes32 _feature, bool _enabled) returns() +func (_SystemConfig *SystemConfigSession) SetFeature(_feature [32]byte, _enabled bool) (*types.Transaction, error) { + return _SystemConfig.Contract.SetFeature(&_SystemConfig.TransactOpts, _feature, _enabled) +} + +// SetFeature is a paid mutator transaction binding the contract method 0xf2c4bc9e. +// +// Solidity: function setFeature(bytes32 _feature, bool _enabled) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetFeature(_feature [32]byte, _enabled bool) (*types.Transaction, error) { + return _SystemConfig.Contract.SetFeature(&_SystemConfig.TransactOpts, _feature, _enabled) +} + +// SetGasConfig is a paid mutator transaction binding the contract method 0x935f029e. +// +// Solidity: function setGasConfig(uint256 _overhead, uint256 _scalar) returns() +func (_SystemConfig *SystemConfigTransactor) SetGasConfig(opts *bind.TransactOpts, _overhead *big.Int, _scalar *big.Int) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setGasConfig", _overhead, _scalar) +} + +// SetGasConfig is a paid mutator transaction binding the contract method 0x935f029e. +// +// Solidity: function setGasConfig(uint256 _overhead, uint256 _scalar) returns() +func (_SystemConfig *SystemConfigSession) SetGasConfig(_overhead *big.Int, _scalar *big.Int) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasConfig(&_SystemConfig.TransactOpts, _overhead, _scalar) +} + +// SetGasConfig is a paid mutator transaction binding the contract method 0x935f029e. +// +// Solidity: function setGasConfig(uint256 _overhead, uint256 _scalar) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetGasConfig(_overhead *big.Int, _scalar *big.Int) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasConfig(&_SystemConfig.TransactOpts, _overhead, _scalar) +} + +// SetGasConfigEcotone is a paid mutator transaction binding the contract method 0x21d7fde5. +// +// Solidity: function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) returns() +func (_SystemConfig *SystemConfigTransactor) SetGasConfigEcotone(opts *bind.TransactOpts, _basefeeScalar uint32, _blobbasefeeScalar uint32) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setGasConfigEcotone", _basefeeScalar, _blobbasefeeScalar) +} + +// SetGasConfigEcotone is a paid mutator transaction binding the contract method 0x21d7fde5. +// +// Solidity: function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) returns() +func (_SystemConfig *SystemConfigSession) SetGasConfigEcotone(_basefeeScalar uint32, _blobbasefeeScalar uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasConfigEcotone(&_SystemConfig.TransactOpts, _basefeeScalar, _blobbasefeeScalar) +} + +// SetGasConfigEcotone is a paid mutator transaction binding the contract method 0x21d7fde5. +// +// Solidity: function setGasConfigEcotone(uint32 _basefeeScalar, uint32 _blobbasefeeScalar) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetGasConfigEcotone(_basefeeScalar uint32, _blobbasefeeScalar uint32) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasConfigEcotone(&_SystemConfig.TransactOpts, _basefeeScalar, _blobbasefeeScalar) +} + +// SetGasLimit is a paid mutator transaction binding the contract method 0xb40a817c. +// +// Solidity: function setGasLimit(uint64 _gasLimit) returns() +func (_SystemConfig *SystemConfigTransactor) SetGasLimit(opts *bind.TransactOpts, _gasLimit uint64) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setGasLimit", _gasLimit) +} + +// SetGasLimit is a paid mutator transaction binding the contract method 0xb40a817c. +// +// Solidity: function setGasLimit(uint64 _gasLimit) returns() +func (_SystemConfig *SystemConfigSession) SetGasLimit(_gasLimit uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasLimit(&_SystemConfig.TransactOpts, _gasLimit) +} + +// SetGasLimit is a paid mutator transaction binding the contract method 0xb40a817c. +// +// Solidity: function setGasLimit(uint64 _gasLimit) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetGasLimit(_gasLimit uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetGasLimit(&_SystemConfig.TransactOpts, _gasLimit) +} + +// SetMinBaseFee is a paid mutator transaction binding the contract method 0x7616f0e8. +// +// Solidity: function setMinBaseFee(uint64 _minBaseFee) returns() +func (_SystemConfig *SystemConfigTransactor) SetMinBaseFee(opts *bind.TransactOpts, _minBaseFee uint64) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setMinBaseFee", _minBaseFee) +} + +// SetMinBaseFee is a paid mutator transaction binding the contract method 0x7616f0e8. +// +// Solidity: function setMinBaseFee(uint64 _minBaseFee) returns() +func (_SystemConfig *SystemConfigSession) SetMinBaseFee(_minBaseFee uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetMinBaseFee(&_SystemConfig.TransactOpts, _minBaseFee) +} + +// SetMinBaseFee is a paid mutator transaction binding the contract method 0x7616f0e8. +// +// Solidity: function setMinBaseFee(uint64 _minBaseFee) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetMinBaseFee(_minBaseFee uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetMinBaseFee(&_SystemConfig.TransactOpts, _minBaseFee) +} + +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigTransactor) SetOperatorFeeScalars(opts *bind.TransactOpts, _operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setOperatorFeeScalars", _operatorFeeScalar, _operatorFeeConstant) +} + +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigSession) SetOperatorFeeScalars(_operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetOperatorFeeScalars(&_SystemConfig.TransactOpts, _operatorFeeScalar, _operatorFeeConstant) +} + +// SetOperatorFeeScalars is a paid mutator transaction binding the contract method 0x155b6c6f. +// +// Solidity: function setOperatorFeeScalars(uint32 _operatorFeeScalar, uint64 _operatorFeeConstant) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetOperatorFeeScalars(_operatorFeeScalar uint32, _operatorFeeConstant uint64) (*types.Transaction, error) { + return _SystemConfig.Contract.SetOperatorFeeScalars(&_SystemConfig.TransactOpts, _operatorFeeScalar, _operatorFeeConstant) +} + +// SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918. +// +// Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns() +func (_SystemConfig *SystemConfigTransactor) SetUnsafeBlockSigner(opts *bind.TransactOpts, _unsafeBlockSigner common.Address) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "setUnsafeBlockSigner", _unsafeBlockSigner) +} + +// SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918. +// +// Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns() +func (_SystemConfig *SystemConfigSession) SetUnsafeBlockSigner(_unsafeBlockSigner common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.SetUnsafeBlockSigner(&_SystemConfig.TransactOpts, _unsafeBlockSigner) +} + +// SetUnsafeBlockSigner is a paid mutator transaction binding the contract method 0x18d13918. +// +// Solidity: function setUnsafeBlockSigner(address _unsafeBlockSigner) returns() +func (_SystemConfig *SystemConfigTransactorSession) SetUnsafeBlockSigner(_unsafeBlockSigner common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.SetUnsafeBlockSigner(&_SystemConfig.TransactOpts, _unsafeBlockSigner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SystemConfig *SystemConfigTransactor) TransferOwnership(opts *bind.TransactOpts, newOwner common.Address) (*types.Transaction, error) { + return _SystemConfig.contract.Transact(opts, "transferOwnership", newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SystemConfig *SystemConfigSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.TransferOwnership(&_SystemConfig.TransactOpts, newOwner) +} + +// TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. +// +// Solidity: function transferOwnership(address newOwner) returns() +func (_SystemConfig *SystemConfigTransactorSession) TransferOwnership(newOwner common.Address) (*types.Transaction, error) { + return _SystemConfig.Contract.TransferOwnership(&_SystemConfig.TransactOpts, newOwner) +} + +// SystemConfigConfigUpdateIterator is returned from FilterConfigUpdate and is used to iterate over the raw logs and unpacked data for ConfigUpdate events raised by the SystemConfig contract. +type SystemConfigConfigUpdateIterator struct { + Event *SystemConfigConfigUpdate // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SystemConfigConfigUpdateIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SystemConfigConfigUpdate) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SystemConfigConfigUpdate) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SystemConfigConfigUpdateIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SystemConfigConfigUpdateIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SystemConfigConfigUpdate represents a ConfigUpdate event raised by the SystemConfig contract. +type SystemConfigConfigUpdate struct { + Version *big.Int + UpdateType uint8 + Data []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterConfigUpdate is a free log retrieval operation binding the contract event 0x1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be. +// +// Solidity: event ConfigUpdate(uint256 indexed version, uint8 indexed updateType, bytes data) +func (_SystemConfig *SystemConfigFilterer) FilterConfigUpdate(opts *bind.FilterOpts, version []*big.Int, updateType []uint8) (*SystemConfigConfigUpdateIterator, error) { + + var versionRule []interface{} + for _, versionItem := range version { + versionRule = append(versionRule, versionItem) + } + var updateTypeRule []interface{} + for _, updateTypeItem := range updateType { + updateTypeRule = append(updateTypeRule, updateTypeItem) + } + + logs, sub, err := _SystemConfig.contract.FilterLogs(opts, "ConfigUpdate", versionRule, updateTypeRule) + if err != nil { + return nil, err + } + return &SystemConfigConfigUpdateIterator{contract: _SystemConfig.contract, event: "ConfigUpdate", logs: logs, sub: sub}, nil +} + +// WatchConfigUpdate is a free log subscription operation binding the contract event 0x1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be. +// +// Solidity: event ConfigUpdate(uint256 indexed version, uint8 indexed updateType, bytes data) +func (_SystemConfig *SystemConfigFilterer) WatchConfigUpdate(opts *bind.WatchOpts, sink chan<- *SystemConfigConfigUpdate, version []*big.Int, updateType []uint8) (event.Subscription, error) { + + var versionRule []interface{} + for _, versionItem := range version { + versionRule = append(versionRule, versionItem) + } + var updateTypeRule []interface{} + for _, updateTypeItem := range updateType { + updateTypeRule = append(updateTypeRule, updateTypeItem) + } + + logs, sub, err := _SystemConfig.contract.WatchLogs(opts, "ConfigUpdate", versionRule, updateTypeRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SystemConfigConfigUpdate) + if err := _SystemConfig.contract.UnpackLog(event, "ConfigUpdate", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseConfigUpdate is a log parse operation binding the contract event 0x1d2b0bda21d56b8bd12d4f94ebacffdfb35f5e226f84b461103bb8beab6353be. +// +// Solidity: event ConfigUpdate(uint256 indexed version, uint8 indexed updateType, bytes data) +func (_SystemConfig *SystemConfigFilterer) ParseConfigUpdate(log types.Log) (*SystemConfigConfigUpdate, error) { + event := new(SystemConfigConfigUpdate) + if err := _SystemConfig.contract.UnpackLog(event, "ConfigUpdate", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SystemConfigFeatureSetIterator is returned from FilterFeatureSet and is used to iterate over the raw logs and unpacked data for FeatureSet events raised by the SystemConfig contract. +type SystemConfigFeatureSetIterator struct { + Event *SystemConfigFeatureSet // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SystemConfigFeatureSetIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SystemConfigFeatureSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SystemConfigFeatureSet) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SystemConfigFeatureSetIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SystemConfigFeatureSetIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SystemConfigFeatureSet represents a FeatureSet event raised by the SystemConfig contract. +type SystemConfigFeatureSet struct { + Feature [32]byte + Enabled bool + Raw types.Log // Blockchain specific contextual infos +} + +// FilterFeatureSet is a free log retrieval operation binding the contract event 0xb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c. +// +// Solidity: event FeatureSet(bytes32 indexed feature, bool indexed enabled) +func (_SystemConfig *SystemConfigFilterer) FilterFeatureSet(opts *bind.FilterOpts, feature [][32]byte, enabled []bool) (*SystemConfigFeatureSetIterator, error) { + + var featureRule []interface{} + for _, featureItem := range feature { + featureRule = append(featureRule, featureItem) + } + var enabledRule []interface{} + for _, enabledItem := range enabled { + enabledRule = append(enabledRule, enabledItem) + } + + logs, sub, err := _SystemConfig.contract.FilterLogs(opts, "FeatureSet", featureRule, enabledRule) + if err != nil { + return nil, err + } + return &SystemConfigFeatureSetIterator{contract: _SystemConfig.contract, event: "FeatureSet", logs: logs, sub: sub}, nil +} + +// WatchFeatureSet is a free log subscription operation binding the contract event 0xb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c. +// +// Solidity: event FeatureSet(bytes32 indexed feature, bool indexed enabled) +func (_SystemConfig *SystemConfigFilterer) WatchFeatureSet(opts *bind.WatchOpts, sink chan<- *SystemConfigFeatureSet, feature [][32]byte, enabled []bool) (event.Subscription, error) { + + var featureRule []interface{} + for _, featureItem := range feature { + featureRule = append(featureRule, featureItem) + } + var enabledRule []interface{} + for _, enabledItem := range enabled { + enabledRule = append(enabledRule, enabledItem) + } + + logs, sub, err := _SystemConfig.contract.WatchLogs(opts, "FeatureSet", featureRule, enabledRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SystemConfigFeatureSet) + if err := _SystemConfig.contract.UnpackLog(event, "FeatureSet", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseFeatureSet is a log parse operation binding the contract event 0xb876f6594132c89891d2fd198e925e999be741ec809abb58bfe9b966876cc06c. +// +// Solidity: event FeatureSet(bytes32 indexed feature, bool indexed enabled) +func (_SystemConfig *SystemConfigFilterer) ParseFeatureSet(log types.Log) (*SystemConfigFeatureSet, error) { + event := new(SystemConfigFeatureSet) + if err := _SystemConfig.contract.UnpackLog(event, "FeatureSet", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SystemConfigInitializedIterator is returned from FilterInitialized and is used to iterate over the raw logs and unpacked data for Initialized events raised by the SystemConfig contract. +type SystemConfigInitializedIterator struct { + Event *SystemConfigInitialized // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SystemConfigInitializedIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SystemConfigInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SystemConfigInitialized) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SystemConfigInitializedIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SystemConfigInitializedIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SystemConfigInitialized represents a Initialized event raised by the SystemConfig contract. +type SystemConfigInitialized struct { + Version uint8 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterInitialized is a free log retrieval operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SystemConfig *SystemConfigFilterer) FilterInitialized(opts *bind.FilterOpts) (*SystemConfigInitializedIterator, error) { + + logs, sub, err := _SystemConfig.contract.FilterLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return &SystemConfigInitializedIterator{contract: _SystemConfig.contract, event: "Initialized", logs: logs, sub: sub}, nil +} + +// WatchInitialized is a free log subscription operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SystemConfig *SystemConfigFilterer) WatchInitialized(opts *bind.WatchOpts, sink chan<- *SystemConfigInitialized) (event.Subscription, error) { + + logs, sub, err := _SystemConfig.contract.WatchLogs(opts, "Initialized") + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SystemConfigInitialized) + if err := _SystemConfig.contract.UnpackLog(event, "Initialized", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseInitialized is a log parse operation binding the contract event 0x7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498. +// +// Solidity: event Initialized(uint8 version) +func (_SystemConfig *SystemConfigFilterer) ParseInitialized(log types.Log) (*SystemConfigInitialized, error) { + event := new(SystemConfigInitialized) + if err := _SystemConfig.contract.UnpackLog(event, "Initialized", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// SystemConfigOwnershipTransferredIterator is returned from FilterOwnershipTransferred and is used to iterate over the raw logs and unpacked data for OwnershipTransferred events raised by the SystemConfig contract. +type SystemConfigOwnershipTransferredIterator struct { + Event *SystemConfigOwnershipTransferred // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *SystemConfigOwnershipTransferredIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(SystemConfigOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(SystemConfigOwnershipTransferred) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *SystemConfigOwnershipTransferredIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *SystemConfigOwnershipTransferredIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// SystemConfigOwnershipTransferred represents a OwnershipTransferred event raised by the SystemConfig contract. +type SystemConfigOwnershipTransferred struct { + PreviousOwner common.Address + NewOwner common.Address + Raw types.Log // Blockchain specific contextual infos +} + +// FilterOwnershipTransferred is a free log retrieval operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SystemConfig *SystemConfigFilterer) FilterOwnershipTransferred(opts *bind.FilterOpts, previousOwner []common.Address, newOwner []common.Address) (*SystemConfigOwnershipTransferredIterator, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _SystemConfig.contract.FilterLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return &SystemConfigOwnershipTransferredIterator{contract: _SystemConfig.contract, event: "OwnershipTransferred", logs: logs, sub: sub}, nil +} + +// WatchOwnershipTransferred is a free log subscription operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SystemConfig *SystemConfigFilterer) WatchOwnershipTransferred(opts *bind.WatchOpts, sink chan<- *SystemConfigOwnershipTransferred, previousOwner []common.Address, newOwner []common.Address) (event.Subscription, error) { + + var previousOwnerRule []interface{} + for _, previousOwnerItem := range previousOwner { + previousOwnerRule = append(previousOwnerRule, previousOwnerItem) + } + var newOwnerRule []interface{} + for _, newOwnerItem := range newOwner { + newOwnerRule = append(newOwnerRule, newOwnerItem) + } + + logs, sub, err := _SystemConfig.contract.WatchLogs(opts, "OwnershipTransferred", previousOwnerRule, newOwnerRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(SystemConfigOwnershipTransferred) + if err := _SystemConfig.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseOwnershipTransferred is a log parse operation binding the contract event 0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0. +// +// Solidity: event OwnershipTransferred(address indexed previousOwner, address indexed newOwner) +func (_SystemConfig *SystemConfigFilterer) ParseOwnershipTransferred(log types.Log) (*SystemConfigOwnershipTransferred, error) { + event := new(SystemConfigOwnershipTransferred) + if err := _SystemConfig.contract.UnpackLog(event, "OwnershipTransferred", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/espresso/cli.go b/espresso/cli.go new file mode 100644 index 00000000000..b16fc7f01c4 --- /dev/null +++ b/espresso/cli.go @@ -0,0 +1,306 @@ +package espresso + +import ( + "crypto/ecdsa" + "fmt" + "strings" + "time" + + espressoStreamers "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 espressoStreamers.Batch]( + cfg CLIConfig, + log log.Logger, + unmarshalBatch func([]byte) (*B, error), +) (*espressoStreamers.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 espressoStreamers.NewEspressoStreamer( + cfg.Namespace, + NewAdaptL1BlockRefClient(l1Client), + NewAdaptL1BlockRefClient(RollupL1Client), + espressoClient, + espressoLightClient, + log, + unmarshalBatch, + cfg.CaffeinationHeightEspresso, + cfg.CaffeinationHeightL2, + cfg.BatchAuthenticatorAddr, + false, + ) +} diff --git a/espresso/ethclient.go b/espresso/ethclient.go new file mode 100644 index 00000000000..38328ce88f8 --- /dev/null +++ b/espresso/ethclient.go @@ -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 +} diff --git a/go.mod b/go.mod index a51b969a148..dfefbe22b90 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ toolchain go1.24.13 require ( github.com/BurntSushi/toml v1.5.0 + github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513 + github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260716191442-1884a718fbf7 github.com/Masterminds/semver/v3 v3.3.1 github.com/andybalholm/brotli v1.1.0 github.com/base/go-bip39 v1.1.0 @@ -21,7 +23,7 @@ require ( github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.4-0.20251001155152-4eb15ccedf7e github.com/ethereum-optimism/superchain-registry/validation v0.0.0-20260115192958-fb86a23cd30e - github.com/ethereum/go-ethereum v1.17.0 + github.com/ethereum/go-ethereum v1.17.1 github.com/fsnotify/fsnotify v1.9.0 github.com/golang/snappy v1.0.0 github.com/google/go-cmp v0.7.0 @@ -32,6 +34,8 @@ require ( github.com/hashicorp/raft v1.7.3 github.com/hashicorp/raft-boltdb/v2 v2.3.1 github.com/hashicorp/raft-wal v0.4.2 + github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 + github.com/hf/nsm v0.0.0-20220930140112-cd181bd646b9 github.com/holiman/uint256 v1.3.2 github.com/ipfs/go-datastore v0.6.0 github.com/ipfs/go-ds-leveldb v0.5.0 @@ -69,7 +73,10 @@ require ( gopkg.in/yaml.v3 v3.0.1 ) -require github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect +require ( + github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20251001021608-1fe7b43fc4d6 // indirect + github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f // indirect +) require ( github.com/benbjohnson/immutable v0.4.0 // indirect @@ -77,10 +84,12 @@ require ( github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf // indirect github.com/fatih/color v1.18.0 // indirect + github.com/fxamacker/cbor/v2 v2.2.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/grafana/pyroscope-go v1.2.7 // indirect github.com/grafana/pyroscope-go/godeltaprof v0.1.9 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/x448/float16 v0.8.4 // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/telemetry v0.0.0-20251111182119-bc8e575c7b54 // indirect ) @@ -113,7 +122,7 @@ require ( github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect github.com/dchest/siphash v1.2.3 // indirect diff --git a/go.sum b/go.sum index e3f1c2994ab..a0e46cb0056 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,10 @@ github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3 github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e h1:ZIWapoIRN1VqT8GR8jAwb1Ie9GyehWjVcGh32Y2MznE= github.com/DataDog/zstd v1.5.6-0.20230824185856-869dae002e5e/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513 h1:4KqlmnDg8pPPj1hCtiLnN0Fh9ltp+PPKOft9I06zQOA= +github.com/EspressoSystems/espresso-network/sdks/go v0.3.5-0.20260410134522-1a819609a513/go.mod h1:kaxR08mJb5Mijy7a2RhWCIWOevFI4PcXwDkzoEbsVTk= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260716191442-1884a718fbf7 h1:PQVHO5oq8/WUjMwVkWB7MM0fqOmfCMQ72ak5fSl6L3s= +github.com/EspressoSystems/espresso-streamers v1.2.1-0.20260716191442-1884a718fbf7/go.mod h1:Op3SNwQnZ3bqwrUXMAORnL2/pNiFzpfOED4ltYs5o/U= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= @@ -171,8 +175,8 @@ github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf h1:GOPo6vn/vTN+3IwZBvXX github.com/coreos/pkg v0.0.0-20220810130054-c7d1c02cb6cf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= -github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc= github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI= github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= @@ -256,6 +260,8 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4 github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.2.0 h1:6eXqdDDe588rSYAi1HfZKbx6YYQO4mxQ9eC6xYpU/JQ= +github.com/fxamacker/cbor/v2 v2.2.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4= @@ -419,6 +425,10 @@ github.com/hashicorp/raft-boltdb/v2 v2.3.1 h1:ackhdCNPKblmOhjEU9+4lHSJYFkJd6Jqyv github.com/hashicorp/raft-boltdb/v2 v2.3.1/go.mod h1:n4S+g43dXF1tqDT+yzcXHhXM6y7MrlUd3TTwGRcUvQE= github.com/hashicorp/raft-wal v0.4.2 h1:DV1jgqEumNfdNpOaZ9mL1Gu7Mz59epFtiE6CoqnHrlY= github.com/hashicorp/raft-wal v0.4.2/go.mod h1:S92ainH+6fRuWk6BtZKJ8EgcGgNTKx48Hk5dhOOY1DM= +github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303 h1:XBSq4rXFUgD8ic6Mr7dBwJN/47yg87XpZQhiknfr4Cg= +github.com/hf/nitrite v0.0.0-20241225144000-c2d5d3c4f303/go.mod h1:ycRhVmo6wegyEl6WN+zXOHUTJvB0J2tiuH88q/McTK8= +github.com/hf/nsm v0.0.0-20220930140112-cd181bd646b9 h1:pU32bJGmZwF4WXb9Yaz0T8vHDtIPVxqDOdmYdwTQPqw= +github.com/hf/nsm v0.0.0-20220930140112-cd181bd646b9/go.mod h1:MJsac5D0fKcNWfriUERtln6segcGfD6Nu0V5uGBbPf8= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db h1:IZUYC/xb3giYwBLMnr8d0TGTzPKFGNTCGgGLoyeX330= github.com/holiman/billy v0.0.0-20250707135307-f2f9b9aae7db/go.mod h1:xTEYN9KCHxuYHs+NmrmzFcnvHMzLLNiGFafCb1n3Mfg= github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= @@ -823,6 +833,8 @@ github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go. github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= +github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f h1:1R9KdKjCNSd7F8iGTxIpoID9prlYH8nuNYKt0XvweHA= +github.com/sigurn/crc8 v0.0.0-20220107193325-2243fe600f9f/go.mod h1:vQhwQ4meQEDfahT5kd61wLAF5AAeh5ZPLVI4JJ/tYo8= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -881,6 +893,8 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.4 h1:0de1OFQxnNqAu+x2FAKKCVIrnfGKQbs7FQz++tB0+Uw= github.com/wlynxg/anet v0.0.4/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4= @@ -956,6 +970,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -1127,6 +1142,7 @@ golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105210202-9ed45478a130/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= diff --git a/op-batcher/batcher/config.go b/op-batcher/batcher/config.go index c6530742453..a682a0586aa 100644 --- a/op-batcher/batcher/config.go +++ b/op-batcher/batcher/config.go @@ -8,6 +8,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/urfave/cli/v2" + "github.com/ethereum-optimism/optimism/espresso" altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-batcher/config" @@ -152,6 +153,10 @@ type CLIConfig struct { PprofConfig oppprof.CLIConfig RPC oprpc.CLIConfig AltDA altda.CLIConfig + + // Espresso groups all TEE-batcher-specific CLI flags. See + // espresso/cli.go for the flag definitions. + Espresso espresso.CLIConfig } func (c *CLIConfig) Check() error { @@ -248,6 +253,7 @@ func NewConfig(ctx *cli.Context) *CLIConfig { PprofConfig: oppprof.ReadCLIConfig(ctx), RPC: oprpc.ReadCLIConfig(ctx), AltDA: altda.ReadCLIConfig(ctx), + Espresso: espresso.ReadCLIConfig(ctx), ThrottleConfig: ThrottleConfig{ AdditionalEndpoints: ctx.StringSlice(flags.AdditionalThrottlingEndpointsFlag.Name), TxSizeLowerLimit: ctx.Uint64(flags.ThrottleTxSizeLowerLimitFlag.Name), diff --git a/op-batcher/batcher/driver.go b/op-batcher/batcher/driver.go index 728e03a9b51..29d0d16e322 100644 --- a/op-batcher/batcher/driver.go +++ b/op-batcher/batcher/driver.go @@ -8,10 +8,14 @@ import ( "math/big" _ "net/http/pprof" "sync" + "sync/atomic" "time" "golang.org/x/sync/errgroup" + espressoStreamers "github.com/EspressoSystems/espresso-streamers/op" + "github.com/EspressoSystems/espresso-streamers/op/derivation" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core" @@ -28,6 +32,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" + oplog "github.com/ethereum-optimism/optimism/op-service/log" "github.com/ethereum-optimism/optimism/op-service/txmgr" ) @@ -85,6 +90,7 @@ func (r txRef) string(txIDStringer func(txID) string) string { type L1Client interface { HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) NonceAt(ctx context.Context, account common.Address, blockNumber *big.Int) (uint64, error) + bind.ContractBackend } type L2Client interface { @@ -112,6 +118,11 @@ type DriverSetup struct { ChannelConfig ChannelConfigProvider AltDA AltDAClient ChannelOutFactory ChannelOutFactory + + // Espresso groups all TEE-batcher-specific runtime state plumbed from + // BatcherService. Defined in espresso_driver.go to keep the upstream + // Optimism field block compact. + Espresso EspressoDriverSetup } // BatchSubmitter encapsulates a service responsible for submitting L2 tx @@ -137,7 +148,7 @@ type BatchSubmitter struct { publishSignal chan pubInfo - // authGroup tracks the fallback batcher's receipt-watcher goroutines (one + // authGroup tracks the fallback and TEE batchers' auth goroutines (one // per auth+batch pair) so the publishing loop can drain them via // waitForAuthGroup before closing receiptsCh. New watchers are back-pressured // (not hard-bounded) by the txmgr Queue: queue.Send blocks at @@ -145,6 +156,18 @@ type BatchSubmitter struct { // though a slow receipts loop can briefly leave more than that parked on their // final receiptsCh send. authGroup sync.WaitGroup + + espressoSubmitter *espressoTransactionSubmitter + espressoStreamer espressoStreamers.EspressoStreamer[derivation.EspressoBatch] + + // clearStateRequested asks the espresso batch loading loop to run clearState + clearStateRequested atomic.Bool + + teeVerifierAddress common.Address + + // degradedLog throttles repeated warnings from tick-driven loops so the + // log debouncer doesn't see the same message every poll interval. + degradedLog *oplog.RepeatStateLogger } // NewBatchSubmitter initializes the BatchSubmitter driver from a preconfigured DriverSetup @@ -157,6 +180,7 @@ func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter { batcher := &BatchSubmitter{ DriverSetup: setup, channelMgr: state, + degradedLog: oplog.NewRepeatStateLogger(), } err := batcher.SetThrottleController(setup.Config.ThrottleParams.ControllerType, setup.Config.ThrottleParams.PIDConfig) @@ -164,6 +188,8 @@ func NewBatchSubmitter(setup DriverSetup) *BatchSubmitter { panic(err) } + batcher.setupEspressoStreamer() + return batcher } @@ -203,6 +229,17 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { publishSignal := make(chan pubInfo, 1) l.publishSignal = publishSignal + if l.Config.Espresso.Enabled { + if err := l.startEspressoLoops(receiptsCh, publishSignal, unsafeBytesUpdated); err != nil { + return err + } + } else { + l.wg.Add(3) + go l.receiptsLoop(l.wg, receiptsCh) // ranges over receiptsCh channel + go l.publishingLoop(l.killCtx, l.wg, receiptsCh, publishSignal) // ranges over publishSignal, spawns routines which send on receiptsCh. Closes receiptsCh when done. + go l.blockLoadingLoop(l.shutdownCtx, l.wg, unsafeBytesUpdated, publishSignal) // sends on unsafeBytesUpdated (if throttling enabled), and publishSignal. Closes them both when done + } + // DA throttling loop should always be started except for testing (indicated by ThrottleThreshold == 0) if l.Config.ThrottleParams.LowerThreshold > 0 { l.wg.Add(1) @@ -211,11 +248,6 @@ func (l *BatchSubmitter) StartBatchSubmitting() error { l.Log.Warn("Throttling loop is DISABLED due to 0 throttle-threshold. This should not be disabled in prod.") } - l.wg.Add(3) - go l.receiptsLoop(l.wg, receiptsCh) // ranges over receiptsCh channel - go l.publishingLoop(l.killCtx, l.wg, receiptsCh, publishSignal) // ranges over publishSignal, spawns routines which send on receiptsCh. Closes receiptsCh when done. - go l.blockLoadingLoop(l.shutdownCtx, l.wg, unsafeBytesUpdated, publishSignal) // sends on unsafeBytesUpdated (if throttling enabled), and publishSignal. Closes them both when done - l.Log.Info("Batch Submitter started") return nil } @@ -831,6 +863,10 @@ func (l *BatchSubmitter) publishStateToL1(ctx context.Context, queue *txmgr.Queu return } + if l.shouldSkipPublishForActiveSeq(ctx) { + return + } + err := l.publishTxToL1(ctx, queue, receiptsCh, daGroup, pi) if err != nil { if err != io.EOF { @@ -856,6 +892,7 @@ func (l *BatchSubmitter) clearState(ctx context.Context) { l.channelMgrMutex.Lock() defer l.channelMgrMutex.Unlock() l.channelMgr.Clear(l1SafeOrigin) + l.resetEspressoStreamer() return true } } diff --git a/op-batcher/batcher/driver_test.go b/op-batcher/batcher/driver_test.go index a1fd5e2b535..90a9e2a8f2f 100644 --- a/op-batcher/batcher/driver_test.go +++ b/op-batcher/batcher/driver_test.go @@ -26,6 +26,7 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testutils" "github.com/ethereum-optimism/optimism/op-service/txmgr" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -485,7 +486,9 @@ func TestBatchSubmitter_CriticalError(t *testing.T) { // ======= ALTDA TESTS ======= // fakeL1Client is just a dummy struct. All fault injection is done via the fakeTxMgr (which doesn't interact with this fakeL1Client). -type fakeL1Client struct{} +type fakeL1Client struct { + bind.ContractBackend +} func (f *fakeL1Client) HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) { if number == nil { diff --git a/op-batcher/batcher/espresso.go b/op-batcher/batcher/espresso.go new file mode 100644 index 00000000000..1bdda9d6766 --- /dev/null +++ b/op-batcher/batcher/espresso.go @@ -0,0 +1,1470 @@ +package batcher + +import ( + "bytes" + "context" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" + "math/big" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" + tagged_base64 "github.com/EspressoSystems/espresso-network/sdks/go/tagged-base64" + espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types" + "github.com/EspressoSystems/espresso-streamers/op/derivation" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/common/math" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/signer/core/apitypes" + + "github.com/ethereum-optimism/optimism/espresso" + "github.com/ethereum-optimism/optimism/espresso/bindings" + "github.com/ethereum-optimism/optimism/op-node/rollup/derive" + "github.com/ethereum-optimism/optimism/op-service/bigs" + "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum-optimism/optimism/op-service/txmgr" +) + +// EspressoOnchainProof is the proof structure returned by the attestation service for onchain verification. +type EspressoOnchainProof struct { + Proof json.RawMessage `json:"proof,omitempty"` + Data json.RawMessage `json:"data,omitempty"` + RawProof struct { + Journal string `json:"journal"` + } `json:"raw_proof"` + OnchainProof string `json:"onchain_proof"` +} + +// espressoSubmitTransactionJob is a struct that holds the state required to +// submit a transaction to Espresso. +// It contains the transaction to be submitted itself, and a number to +// track the total number of attempts to submit this transaction to Espresso. +type espressoSubmitTransactionJob struct { + attempts int + transaction *espressoCommon.Transaction +} + +// espressoSubmitTransactionJobResponse is a struct that holds the +// response from the Espresso client after submitting a transaction. +// It contains the job that was submitted, the hash of the transaction +// that was submitted (if successful), and any error that occurred during the +// submission (if unsuccessful). +type espressoSubmitTransactionJobResponse struct { + job espressoSubmitTransactionJob + hash *espressoCommon.TaggedBase64 + err error +} + +// espressoTransactionJobAttempt is a struct that holds the job and +// response channel for a transaction submission job. +// +// This is the unit of work that is submitted to the worker to process +// for transaction submissions. +type espressoTransactionJobAttempt struct { + job espressoSubmitTransactionJob + resp chan espressoSubmitTransactionJobResponse +} + +// espressoVerifyReceiptJob is a struct that holds the state required to +// verify a receipt for a transaction that was submitted to Espresso. +// It contains the transaction that was submitted, the hash of the +// transaction, and the number of attempts to verify the receipt. +type espressoVerifyReceiptJob struct { + attempts int + startHeight uint64 // HotShot block height when verification began (set on first attempt) + startTime time.Time // wall-clock time when verification began (safety backstop) + transaction espressoSubmitTransactionJob + hash *espressoCommon.TaggedBase64 +} + +// espressoVerifyReceiptJobResponse is a struct that holds the +// response from the Espresso client after verifying a receipt. +// It contains the job that was submitted, and any error that occurred +// during the verification (if unsuccessful). +type espressoVerifyReceiptJobResponse struct { + job espressoVerifyReceiptJob + err error + currentHeight uint64 // latest known HotShot block height at time of verification attempt +} + +// espressoVerifyReceiptJobAttempt is a struct that holds the job and +// response channel for a receipt verification job. +// +// This is the unit of work that is submitted to the worker to process +// for receipt verifications. +type espressoVerifyReceiptJobAttempt struct { + job espressoVerifyReceiptJob + resp chan espressoVerifyReceiptJobResponse +} + +// espressoTransactionSubmitter is a struct that holds the state that governs +// the worker queue processing details for submitting transactions to Espresso +// without spawning arbitrarily many goroutines. +type espressoTransactionSubmitter struct { + ctx context.Context + wg *sync.WaitGroup + submitJobQueue chan espressoSubmitTransactionJob + submitRespQueue chan espressoSubmitTransactionJobResponse + submitWorkerQueue chan chan espressoTransactionJobAttempt + verifyReceiptJobQueue chan espressoVerifyReceiptJob + verifyReceiptRespQueue chan espressoVerifyReceiptJobResponse + verifyReceiptWorkerQueue chan chan espressoVerifyReceiptJobAttempt + espresso espressoClient.EspressoClient + latestBlockHeight atomic.Uint64 // shared HotShot block height, updated by trackBlockHeight + verifyReceiptMaxBlocks uint64 + verifyReceiptSafetyTimeout time.Duration + verifyReceiptRetryDelay time.Duration + numInFlightJobs atomic.Int64 + numMaxInFlightJobs int +} + +// EspressoTransactionSubmitterConfig is a configuration struct for the +// EspressoTransactionSubmitter. It contains the configurable details for +// creating the EspressoTransactionSubmitter. +type EspressoTransactionSubmitterConfig struct { + Ctx context.Context + EspressoClient espressoClient.EspressoClient + Wg *sync.WaitGroup + SubmitJobQueueCapacity int + SubmitResponseQueueCapacity int + VerifyReceiptJobQueueCapacity int + VerifyReceiptResponseQueueCapacity int + VerifyReceiptMaxBlocks uint64 + VerifyReceiptSafetyTimeout time.Duration + VerifyReceiptRetryDelay time.Duration + MaxInFlightJobs int +} + +// EspressoTransactionSubmitterOption is a function that can be used to +// configure the EspressoTransactionSubmitterConfig. +type EspressoTransactionSubmitterOption func(*EspressoTransactionSubmitterConfig) + +// WithContext is an option that can be used to set the Espresso client +// for the EspressoTransactionSubmitterConfig. +func WithContext(ctx context.Context) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.Ctx = ctx + } +} + +// WithEspressoClient is an option that can be used to set the Espresso client +// for the EspressoTransactionSubmitterConfig. +func WithEspressoClient(client espressoClient.EspressoClient) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.EspressoClient = client + } +} + +// WithWaitGroup is an option that can be used to set the wait group +// for the EspressoTransactionSubmitterConfig. +func WithWaitGroup(wg *sync.WaitGroup) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.Wg = wg + } +} + +// WithVerifyReceiptMaxBlocks sets the number of HotShot blocks to wait for a +// submitted transaction to become queryable before re-submitting. +func WithVerifyReceiptMaxBlocks(n uint64) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.VerifyReceiptMaxBlocks = n + } +} + +// WithVerifyReceiptSafetyTimeout sets the wall-clock backstop for receipt +// verification. If the block height tracker is stale or broken, re-submission +// is triggered after this duration. +func WithVerifyReceiptSafetyTimeout(d time.Duration) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.VerifyReceiptSafetyTimeout = d + } +} + +// WithVerifyReceiptRetryDelay sets the delay between receipt verification retries. +func WithVerifyReceiptRetryDelay(d time.Duration) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.VerifyReceiptRetryDelay = d + } +} + +// WithMaxInFlightJobs sets the maximum number of inflight requests to +// have at once. Once at capacity all new submission attempts will +// automatically fail. +func WithMaxInFlightJobs(n int) EspressoTransactionSubmitterOption { + return func(config *EspressoTransactionSubmitterConfig) { + config.MaxInFlightJobs = n + } +} + +// NewEspressoTransactionSubmitter creates a new EspressoTransactionSubmitter +// throttle with the given context and espresso client. It will create a new +// transaction submitter with some default options, and apply those options to +// the configuration. +// +// The resulting instance should reflect the given configuration. +// After returning, the caller should call SpawnWorkers to start the workers, +// and Start to start the job scheduling and response handling portions of the +// transaction submitter. After that, the user should be able to submit +// transactions to the submitter via the SubmitTransaction method. +func NewEspressoTransactionSubmitter(options ...EspressoTransactionSubmitterOption) *espressoTransactionSubmitter { + config := EspressoTransactionSubmitterConfig{ + Ctx: context.Background(), + Wg: new(sync.WaitGroup), + SubmitJobQueueCapacity: espresso.DefaultMaxInFlightRequestsToEspresso, + SubmitResponseQueueCapacity: 10, + VerifyReceiptJobQueueCapacity: espresso.DefaultMaxInFlightRequestsToEspresso, + VerifyReceiptResponseQueueCapacity: 10, + VerifyReceiptMaxBlocks: espresso.DefaultVerifyReceiptMaxBlocks, + VerifyReceiptSafetyTimeout: espresso.DefaultVerifyReceiptSafetyTimeout, + VerifyReceiptRetryDelay: espresso.DefaultVerifyReceiptRetryDelay, + MaxInFlightJobs: espresso.DefaultMaxInFlightRequestsToEspresso, + } + + for _, option := range options { + option(&config) + } + + if config.EspressoClient == nil { + panic("Espresso client is required") + } + + return &espressoTransactionSubmitter{ + ctx: config.Ctx, + wg: config.Wg, + submitJobQueue: make(chan espressoSubmitTransactionJob, config.SubmitJobQueueCapacity), + submitRespQueue: make(chan espressoSubmitTransactionJobResponse, config.SubmitResponseQueueCapacity), + submitWorkerQueue: make(chan chan espressoTransactionJobAttempt), + verifyReceiptJobQueue: make(chan espressoVerifyReceiptJob, config.VerifyReceiptJobQueueCapacity), + verifyReceiptRespQueue: make(chan espressoVerifyReceiptJobResponse, config.VerifyReceiptResponseQueueCapacity), + verifyReceiptWorkerQueue: make(chan chan espressoVerifyReceiptJobAttempt), + espresso: config.EspressoClient, + verifyReceiptMaxBlocks: config.VerifyReceiptMaxBlocks, + verifyReceiptSafetyTimeout: config.VerifyReceiptSafetyTimeout, + verifyReceiptRetryDelay: config.VerifyReceiptRetryDelay, + numInFlightJobs: atomic.Int64{}, + numMaxInFlightJobs: config.MaxInFlightJobs, + } +} + +// ErrTooManyInFlightRequests is an error that is returned when there are +// too many requests in flight at once right now. +type ErrTooManyInFlightRequests struct { + NumInFlightRequests int + MaxInFlightRequests int +} + +// Error implements error +func (e ErrTooManyInFlightRequests) Error() string { + return fmt.Sprintf("too many requests in flight to espresso, in flight requests: %d, maximum allowed: %d", e.NumInFlightRequests, e.MaxInFlightRequests) +} + +// ErrSubmitToEspressoChannelFull is an error that is returned when the channel +// to submit a transaction to the espresso job channel is full. +// +// This ultimately means that the channel buffer size of the job channel is +// smaller than the max number of in flight requests allowed. +type ErrSubmitToEspressoChannelFull struct { + Capacity int + Len int +} + +// Error implements error +func (e ErrSubmitToEspressoChannelFull) Error() string { + return fmt.Sprintf("submit transaction to espresso job channel is full, len: %d, capacity: %d", e.Len, e.Capacity) +} + +// SubmitTransaction will submit a transaction to the Job queue. +// +// NOTE: There is a limit on the maximum number of inflight requests we allow +// at once. If we're over or at capacity, we'll return an error indicating so. +// If we have capacity available, we'll attempt to submit to the channel, and +// if we're unable to, we'll return an error. This will **NOT** block. +func (s *espressoTransactionSubmitter) SubmitTransaction(job *espressoCommon.Transaction) error { + // Check to see if we're over capacity, and if we are, then immediately + // return an error. + if numInFlightRequests, numMaxInFlightRequests := s.numInFlightJobs.Load(), int64(s.numMaxInFlightJobs); numInFlightRequests >= numMaxInFlightRequests { + return ErrTooManyInFlightRequests{ + NumInFlightRequests: int(numInFlightRequests), + MaxInFlightRequests: int(numMaxInFlightRequests), + } + } + + // Construct the job submission + jobSubmission := espressoSubmitTransactionJob{ + transaction: job, + } + + select { + default: + return ErrSubmitToEspressoChannelFull{ + Len: len(s.submitJobQueue), + Capacity: cap(s.submitJobQueue), + } + case s.submitJobQueue <- jobSubmission: + // increment in flight requests + s.numInFlightJobs.Add(1) + return nil + } +} + +// Evaluation result for a job. +type JobEvaluation int + +const ( + // Continue handling the current job. + Handle JobEvaluation = iota + // Retry the submission. + RetrySubmission + // Retry the verification. + RetryVerification + // Skip the current job and proceed to the next one. + Skip +) + +// Evaluate the submission job. +// +// # Returns +// +// * If there is no error: Handle. +// +// * If there is a permanent issue that won't be fixed by a retry: Skip. +// +// * Otherwise: RetrySubmission. +func evaluateSubmission(jobResp espressoSubmitTransactionJobResponse) JobEvaluation { + err := jobResp.err + + // If there's no error, continue handling the submission. + if err == nil { + return Handle + } + + if errors.Is(err, espressoClient.ErrPermanent) { + return Skip + } + + if !errors.Is(err, espressoClient.ErrEphemeral) { + // Log the warning for a potentially missed error handling, but still retry it. + log.Warn("error not explicitly marked as retryable or not", "err", err) + } + + // Otherwise, retry the submission. + return RetrySubmission +} + +// handleTransactionSubmitJobResponse is a function that is meant to be run in a +// goroutine. +// +// It handles the responses from the submit transaction jobs. It will +// determine if the transaction was successfully submitted to Espresso, and +// if not, it will retry the transaction. If the transaction was successfully +// submitted, it will then submit a job to the verify receipt job queue to +// verify the receipt of the transaction. +func (s *espressoTransactionSubmitter) handleTransactionSubmitJobResponse() { + ticker := time.NewTicker(10 * time.Minute) + defer ticker.Stop() + + for { + var jobResp espressoSubmitTransactionJobResponse + var ok bool + + select { + case <-s.ctx.Done(): + return + case <-ticker.C: + log.Debug("Espresso transaction submitter queue status", + "submitJobQueue", len(s.submitJobQueue), + "submitRespQueue", len(s.submitRespQueue), + "verifyReceiptJobQueue", len(s.verifyReceiptJobQueue), + "verifyReceiptRespQueue", len(s.verifyReceiptRespQueue)) + continue + case jobResp, ok = <-s.submitRespQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + switch evaluation := evaluateSubmission(jobResp); evaluation { + case Skip: + s.numInFlightJobs.Add(-1) + continue + case RetrySubmission: + s.submitJobQueue <- jobResp.job + continue + } + + verifyJob := espressoVerifyReceiptJob{ + startTime: time.Now(), + transaction: jobResp.job, + hash: jobResp.hash, + } + + select { + case <-s.ctx.Done(): + return + // Move to verifying the receipt + case s.verifyReceiptJobQueue <- verifyJob: + } + } +} + +// Default values for receipt verification tuning are defined as exported +// constants in the espresso package (espresso.DefaultVerifyReceipt*) so that +// the CLI flag defaults and this batcher logic share a single source of truth. + +// evaluateVerification evaluates the verification job response. +// +// # Returns +// +// * If there is no error: Handle. +// +// * If there is a permanent issue that won't be fixed by a retry: Skip. +// +// * If enough HotShot blocks have passed since verification started: RetrySubmission. +// +// * If the wall-clock safety timeout is exceeded: RetrySubmission. +// +// * Otherwise: RetryVerification. +func (s *espressoTransactionSubmitter) evaluateVerification(jobResp espressoVerifyReceiptJobResponse) JobEvaluation { + err := jobResp.err + + // If there's no error, continue handling the verification. + if err == nil { + return Handle + } + + if errors.Is(err, espressoClient.ErrPermanent) { + return Skip + } + + if !errors.Is(err, espressoClient.ErrEphemeral) { + // Log the warning for a potentially missed error handling, but still retry it. + log.Warn("error not explicitly marked as retryable or not", "err", err) + } + + // Block-count-based timeout: re-submit if enough HotShot blocks have + // passed since verification started. The startHeight guard handles the + // edge case where the height tracker hasn't fetched its first value yet. + if jobResp.job.startHeight > 0 && jobResp.currentHeight >= jobResp.job.startHeight+s.verifyReceiptMaxBlocks { + log.Info("Verification timed out by block count, re-submitting", + "startHeight", jobResp.job.startHeight, + "currentHeight", jobResp.currentHeight, + "maxBlocks", s.verifyReceiptMaxBlocks) + return RetrySubmission + } + + // Wall-clock safety backstop in case the block height tracker is stale + // or broken (e.g., query service returning old data). + if elapsed := time.Since(jobResp.job.startTime); elapsed > s.verifyReceiptSafetyTimeout { + log.Warn("Verification timed out by safety timeout, re-submitting", + "elapsed", elapsed, + "safetyTimeout", s.verifyReceiptSafetyTimeout) + return RetrySubmission + } + + // Otherwise, retry the verification. + return RetryVerification +} + +// handleVerifyReceiptJobResponse is a function that is meant to be run in a +// goroutine. +// +// This function handles responses from the verify receipt job queue. It will +// check the results for any errors, and if there are any errors that are +// applicable to retry, it will requeue the job for another attempt. +// If the the job is successful, no further processing is needed and it is +// considered complete. +// If the job has taken too long to verify, then it will re-submit the job +// back to the submit transaction queue for another attempt. +// +// NOTE: This function currently will loop forever if the transaction is +// never going to be available. +func (s *espressoTransactionSubmitter) handleVerifyReceiptJobResponse() { + for { + var jobResp espressoVerifyReceiptJobResponse + var ok bool + + select { + case <-s.ctx.Done(): + return + case jobResp, ok = <-s.verifyReceiptRespQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + switch evaluation := s.evaluateVerification(jobResp); evaluation { + case Skip: + // decrement in flight jobs on skip, since we're done with this job + s.numInFlightJobs.Add(-1) + continue + case RetrySubmission: + s.submitJobQueue <- jobResp.job.transaction + continue + case RetryVerification: + s.verifyReceiptJobQueue <- jobResp.job + continue + } + + s.numInFlightJobs.Add(-1) + // We're done with this job and transaction, we have successfully + // confirmed that the transaction was submitted to Espresso + commitment := jobResp.job.transaction.transaction.Commit() + hash, _ := tagged_base64.New("TX", commitment[:]) + log.Info("Transaction confirmed on Espresso", "hash", hash.String()) + } +} + +// scheduleSubmitTransactionJobs is a function that is meant to be run in a +// goroutine. +// +// It handles the scheduling of submit transaction jobs so that the submit +// transaction workers can process them. +func (s *espressoTransactionSubmitter) scheduleSubmitTransactionJobs() { + for { + var ok bool + + // Get a worker from the worker queue + var worker chan espressoTransactionJobAttempt + select { + case <-s.ctx.Done(): + return + + case worker, ok = <-s.submitWorkerQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + // Get a job from the job queue + var job espressoSubmitTransactionJob + select { + case <-s.ctx.Done(): + return + case job, ok = <-s.submitJobQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + // Submit the job to the worker + select { + case <-s.ctx.Done(): + return + + case worker <- espressoTransactionJobAttempt{job: job, resp: s.submitRespQueue}: + } + } +} + +// scheduleVerifyReceiptJobs is a function that is meant to be run in a +// goroutine. +// +// It handles the scheduling of verify receipt jobs so that the verify receipt +// workers can process them. +func (s *espressoTransactionSubmitter) scheduleVerifyReceiptsJobs() { + for { + var ok bool + + // Get a worker from the worker queue + var worker chan espressoVerifyReceiptJobAttempt + select { + case <-s.ctx.Done(): + return + + case worker, ok = <-s.verifyReceiptWorkerQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + // Get a job from the job queue + var job espressoVerifyReceiptJob + select { + case <-s.ctx.Done(): + return + case job, ok = <-s.verifyReceiptJobQueue: + if !ok { + // Our channel is closed, and we are done + return + } + } + + // Submit the job to the worker + select { + case <-s.ctx.Done(): + return + + case worker <- espressoVerifyReceiptJobAttempt{job: job, resp: s.verifyReceiptRespQueue}: + } + } +} + +// espressoSubmitTransactionWorker is a function that is meant to be run as a +// goroutine. It will create a channel for it's job queue, and submit those to +// the worker queue in order to wait for work. It will then take that job and +// attempt to submit the transaction contained within to espresso using the +// given espresso client. It will submit the response back to the channel +// contained within the job attempt it received. +// +// It's lifetime is governed by the context passed to it, and it will stop +// processing when that context is cancelled. +// +// NOTE: If the context is cancelled after a job has been received, but before +// it is able to submit the transaction, or report about it's result, the job +// may be lost. +func espressoSubmitTransactionWorker( + ctx context.Context, + wg *sync.WaitGroup, + cli espressoClient.EspressoClient, + workerQueue chan<- chan espressoTransactionJobAttempt, +) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + defer wg.Done() + ch := make(chan espressoTransactionJobAttempt) + + for { + select { + case <-ctx.Done(): + return + + // Queue our job queue, asking for work + case workerQueue <- ch: + } + + // Wait for a job to run + var jobAttempt espressoTransactionJobAttempt + select { + case <-ctx.Done(): + return + case jobAttempt = <-ch: + } + + // Submit the transaction to Espresso + hash, err := cli.SubmitTransaction(ctx, *jobAttempt.job.transaction) + if err == nil { + log.Info("Submitted transaction to Espresso", "hash", hash) + } + + jobAttempt.job.attempts++ + resp := espressoSubmitTransactionJobResponse{ + job: jobAttempt.job, + hash: hash, + err: err, + } + + select { + case <-ctx.Done(): + return + + // Send the response back via the channel in the job attempt struct + case jobAttempt.resp <- resp: + } + } +} + +// espressoVerifyTransactionWorker is a function that is meant to be run as a +// goroutine. It will create a channel for it's job queue, and submit those to +// the worker queue in order to wait for work. It will then take that job and +// attempt to verify the transaction contained within to espresso using the +// given espresso client. It will submit the response back to the channel +// contained within the job attempt it received. +func espressoVerifyTransactionWorker( + ctx context.Context, + wg *sync.WaitGroup, + cli espressoClient.EspressoClient, + workerQueue chan<- chan espressoVerifyReceiptJobAttempt, + latestHeight *atomic.Uint64, + retryDelay time.Duration, +) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + defer wg.Done() + ch := make(chan espressoVerifyReceiptJobAttempt) + + for { + select { + case <-ctx.Done(): + return + + // Queue our job queue, asking for work + case workerQueue <- ch: + } + + // Wait for a job to run + var jobAttempt espressoVerifyReceiptJobAttempt + select { + case <-ctx.Done(): + return + case jobAttempt = <-ch: + } + + // On the first attempt, snapshot the current block height so we + // can measure how many blocks pass during verification. + if jobAttempt.job.attempts == 0 { + jobAttempt.job.startHeight = latestHeight.Load() + } + + if jobAttempt.job.attempts > 0 { + // We have already attempted this job, so we will wait a bit + // NOTE: this prevents this worker from being able to process + // other jobs while we wait for this delay. + time.Sleep(retryDelay) + } + + _, err := cli.FetchTransactionByHash(ctx, jobAttempt.job.hash) + + jobAttempt.job.attempts++ + resp := espressoVerifyReceiptJobResponse{ + job: jobAttempt.job, + err: err, + currentHeight: latestHeight.Load(), + } + + select { + case <-ctx.Done(): + return + + case jobAttempt.resp <- resp: + } + } +} + +// SpawnWorkers spawns the given number of workers to process the +// submit transaction jobs and verify receipt jobs. +func (s *espressoTransactionSubmitter) SpawnWorkers(numSubmitTransactionWorkers, numVerifyReceiptWorkers int) { + workersCtx := s.ctx + + for i := 0; i < numSubmitTransactionWorkers; i++ { + s.wg.Add(1) + go espressoSubmitTransactionWorker(workersCtx, s.wg, s.espresso, s.submitWorkerQueue) + } + + for i := 0; i < numVerifyReceiptWorkers; i++ { + s.wg.Add(1) + go espressoVerifyTransactionWorker(workersCtx, s.wg, s.espresso, s.verifyReceiptWorkerQueue, &s.latestBlockHeight, s.verifyReceiptRetryDelay) + } +} + +// trackBlockHeight periodically polls FetchLatestBlockHeight and stores +// the result in s.latestBlockHeight for verify jobs to compare against. +// This avoids redundant height queries from individual verify workers. +func (s *espressoTransactionSubmitter) trackBlockHeight() { + for { + height, err := s.espresso.FetchLatestBlockHeight(s.ctx) + if err == nil { + s.latestBlockHeight.Store(height) + } else if s.ctx.Err() == nil { + log.Debug("failed to fetch latest block height for verification tracking", "err", err) + } + + // Wait for the next interval or until context is done. + select { + case <-time.After(s.verifyReceiptRetryDelay): + case <-s.ctx.Done(): + return + } + } +} + +func (s *espressoTransactionSubmitter) Start() { + // Block height tracker for verify receipt timeout + go s.trackBlockHeight() + + // Submit Transaction Jobs + go s.scheduleSubmitTransactionJobs() + go s.handleTransactionSubmitJobResponse() + + // Verify Receipt Jobs + go s.scheduleVerifyReceiptsJobs() + go s.handleVerifyReceiptJobResponse() +} + +// Converts a block to an EspressoBatch and starts a goroutine that publishes it to Espresso +// Returns error only if batch conversion fails, otherwise it is infallible, as the goroutine +// will retry publishing until successful. +func (l *BatchSubmitter) queueBlockToEspresso(ctx context.Context, block *types.Block) error { + espressoBatch, err := derivation.BlockToEspressoBatch(l.RollupConfig, block) + if err != nil { + l.Log.Warn("Failed to derive batch from block", "err", err) + return fmt.Errorf("failed to derive batch from block: %w", err) + } + + transaction, err := espressoBatch.ToEspressoTransaction(ctx, bigs.Uint64Strict(l.RollupConfig.L2ChainID), l.Espresso.ChainSigner) + if err != nil { + l.Log.Warn("Failed to create Espresso transaction from a batch", "err", err) + return fmt.Errorf("failed to create Espresso transaction from a batch: %w", err) + } + + commitment := transaction.Commit() + hash, _ := tagged_base64.New("TX", commitment[:]) + l.Log.Info("Created Espresso transaction from batch", "hash", hash, "batchNr", bigs.Uint64Strict(espressoBatch.BatchHeader.Number)) + + if err := l.espressoSubmitter.SubmitTransaction(transaction); err != nil { + return fmt.Errorf("failed to submit job to espresso: %w", err) + } + + return nil +} + +func (l *BatchSubmitter) espressoSyncAndRefresh(ctx context.Context, newSyncStatus *eth.SyncStatus) { + err := l.EspressoStreamer().Refresh(ctx, newSyncStatus.FinalizedL1, newSyncStatus.SafeL2.Number, newSyncStatus.FinalizedL2.L1Origin) + if err != nil { + l.degradedLog.Warn(l.Log, "espressoStreamerRefreshErr", "Failed to refresh Espresso streamer", "err", err) + } else { + l.degradedLog.Clear(l.Log, "espressoStreamerRefreshErr", "Espresso streamer refresh recovered") + } + + l.channelMgrMutex.Lock() + defer l.channelMgrMutex.Unlock() + syncActions, outOfSync := computeSyncActions(*newSyncStatus, l.prevCurrentL1, l.channelMgr.blocks, l.channelMgr.channelQueue, l.Log) + if outOfSync { + l.degradedLog.Warn(l.Log, "sequencerOutOfSync", "Sequencer is out of sync, retrying next tick.") + return + } + l.degradedLog.Clear(l.Log, "sequencerOutOfSync", "Sequencer back in sync") + l.prevCurrentL1 = newSyncStatus.CurrentL1 + if syncActions.clearState != nil { + l.channelMgr.Clear(*syncActions.clearState) + l.EspressoStreamer().Reset() + } else { + l.channelMgr.PruneSafeBlocks(syncActions.blocksToPrune) + l.channelMgr.PruneChannels(syncActions.channelsToPrune) + } +} + +// peekNextBatch returns the next batch from the streamer, performing a fork check +// against an expected parent hash. +// +// The expected parent is tip when tip is set. When tip is zero (channel manager was +// just cleared), we fall back to safeL2.Hash if the batch is at exactly safeL2+1 — +// the one position where we can set tip to the known safe head. Otherwise we accept the batch as-is. +func (l *BatchSubmitter) peekNextBatch(ctx context.Context, syncStatus *eth.SyncStatus) *derivation.EspressoBatch { + l.channelMgrMutex.Lock() + tip := l.channelMgr.tip + l.channelMgrMutex.Unlock() + + batch := l.EspressoStreamer().Peek(ctx) + if batch == nil { + return nil + } + + // Check if we can set the tip if not set + if tip == (common.Hash{}) && (*batch).Number() == syncStatus.SafeL2.Number+1 { + l.Log.Info( + "setting tip to safe l2 hash", + "batchNr", (*batch).Number(), + "batchParent", (*batch).Header().ParentHash.Hex(), + "tip", tip, + ) + tip = syncStatus.SafeL2.Hash + } + + if tip == (common.Hash{}) { + l.Log.Warn( + "tip is not set, taking available batch", + "blockParentHash", (*batch).Header().ParentHash.Hex(), + "blockHash", (*batch).Header().Hash().Hex(), + ) + return batch + } + + if (*batch).Header().ParentHash != tip { + l.Log.Warn( + "head batch fork mismatch, seeking to proper head", + "batchNr", (*batch).Number(), + "batchParent", (*batch).Header().ParentHash, + "tip", tip, + ) + l.EspressoStreamer().SetProperHead(tip) + return nil + } + + return batch +} + +// requestClearState asks the batch loading loop to perform `l.clearState`. +func (l *BatchSubmitter) requestClearState() { + l.clearStateRequested.Store(true) +} + +// performClearState runs clearState if it was requested via requestClearState, +// reporting whether a clear was performed. +func (l *BatchSubmitter) performClearState(ctx context.Context) bool { + if !l.clearStateRequested.CompareAndSwap(true, false) { + return false + } + l.Log.Info("Clearing state as requested by the block queueing loop") + l.clearState(ctx) + return true +} + +// Periodically refreshes the sync status and polls Espresso streamer for new batches. +// Owns publishSignal and unsafeBytesUpdated: it is their only closer, so the loops +// ranging over them (publishingLoop, throttlingLoop) terminate when this loop exits. +func (l *BatchSubmitter) espressoBatchLoadingLoop(ctx context.Context, wg *sync.WaitGroup, publishSignal chan pubInfo, unsafeBytesUpdated chan int64) { + l.Log.Info("Starting EspressoBatchLoadingLoop", "polling interval", l.Config.Espresso.PollInterval) + + defer wg.Done() + ticker := time.NewTicker(l.Config.Espresso.PollInterval) + defer ticker.Stop() + defer close(publishSignal) + defer close(unsafeBytesUpdated) + + for { + select { + case <-ticker.C: + // Check if block loader requested to clear state + l.performClearState(ctx) + + newSyncStatus, err := l.getSyncStatus(ctx) + if err != nil { + l.degradedLog.Warn(l.Log, "syncStatusErr/espressoBatchLoading", "failed to refresh sync status", "err", err) + continue + } + l.degradedLog.Clear(l.Log, "syncStatusErr/espressoBatchLoading", "sync status fetch recovered") + + l.espressoSyncAndRefresh(ctx, newSyncStatus) + + err = l.EspressoStreamer().Update(ctx) + + var batch *derivation.EspressoBatch + blocksAdded := 0 + + for { + // Check if block loader requested to clear state + if l.performClearState(ctx) { + break + } + + batch = l.peekNextBatch(ctx, newSyncStatus) + + if batch == nil { + break + } + + // This should happen ONLY if the batch is malformed. ToBlock has to guarantee no + // transient errors. + block, err := batch.ToBlock(l.RollupConfig) + if err != nil { + l.Log.Error("failed to convert singular batch to block", "err", err) + l.EspressoStreamer().Next(ctx) + continue + } + + l.Log.Info( + "Received block from Espresso", + "blockNr", block.NumberU64(), + "blockHash", block.Hash(), + "parentHash", block.ParentHash(), + ) + + l.channelMgrMutex.Lock() + err = l.channelMgr.AddL2Block(block) + l.channelMgrMutex.Unlock() + + if err != nil { + l.Log.Error("failed to add L2 block to channel manager", "err", err) + l.clearState(ctx) + l.EspressoStreamer().Reset() + break + } + + l.EspressoStreamer().Next(ctx) + l.Log.Info("Added L2 block to channel manager", "blockNr", block.NumberU64()) + + // During a large drain, signal periodically so throttling can engage + // before the whole backlog is consumed (mirrors loadBlocksIntoState). + blocksAdded++ + if blocksAdded%100 == 0 { + l.sendToThrottlingLoop(unsafeBytesUpdated) + } + } + + l.sendToThrottlingLoop(unsafeBytesUpdated) + l.tryPublishSignal(publishSignal, pubInfo{}) + + // A failure in the streamer Update can happen after the buffer has been partially filled + if err != nil { + l.degradedLog.Warn(l.Log, "espressoStreamerUpdateErr", "failed to update Espresso streamer", "err", err) + continue + } + l.degradedLog.Clear(l.Log, "espressoStreamerUpdateErr", "Espresso streamer update recovered") + + case <-ctx.Done(): + l.Log.Info("espressoBatchLoadingLoop returning") + return + } + } +} + +type BlockLoader struct { + queuedBlocks []eth.L2BlockRef + prevSyncStatus *eth.SyncStatus + batcher *BatchSubmitter +} + +func (l *BlockLoader) reset() { + l.prevSyncStatus = nil + l.queuedBlocks = nil + l.batcher.requestClearState() +} + +func (l *BlockLoader) EnqueueBlocks(ctx context.Context, blocksToQueue inclusiveBlockRange) { + l.batcher.Log.Debug("Loading and queueing blocks", "range", blocksToQueue) + for i := blocksToQueue.start; i <= blocksToQueue.end; i++ { + block, err := l.batcher.fetchBlock(ctx, i) + if err != nil { + l.batcher.degradedLog.Warn(l.batcher.Log, "fetchBlockErr", "Failed to fetch block", "err", err) + break + } + l.batcher.degradedLog.Clear(l.batcher.Log, "fetchBlockErr", "Block fetching recovered") + + for _, txn := range block.Transactions() { + l.batcher.Log.Debug("tx hash before submitting to Espresso", "hash", txn.Hash().String()) + } + + if len(l.queuedBlocks) > 0 && block.ParentHash() != l.queuedBlocks[len(l.queuedBlocks)-1].Hash { + l.batcher.Log.Warn("Found L2 reorg", "block_number", i) + l.reset() + break + } + + blockRef, err := derive.L2BlockToBlockRef(l.batcher.RollupConfig, block) + if err != nil { + // NOTE: if we fail to convert an L2Block to a BlockRef, it's + // unlikely that breaking here, and waiting for resubmission would + // actually ever result in it succeeding. + // For now, we add a log, but this may be a Fatal unrecoverable + // error if it ever occurs. + l.batcher.Log.Warn("failed to convert block to block reference", "err", err) + break + } + + err = l.batcher.queueBlockToEspresso(ctx, block) + if err != nil { + l.batcher.Log.Debug("queue block to espresso failed", "err", err) + break + } + + l.queuedBlocks = append(l.queuedBlocks, blockRef) + } +} + +type EnqueueBlockAction uint + +const ( + ActionEnqueue = iota + ActionRetry + ActionReset +) + +// This function is an analogue of `computeSyncActions` for Espresso batcher mode +// +// It computes the next block range to enqueue to Espresso based on new newSyncStatus and +// does a number of checks to ensure consistency of the chain. +// +// If reorg is detected, empty range and ActionReset is returned. +// If there isn't enough information or no blocks to load yet, empty range and ActionRetry is returned. +func (l *BlockLoader) nextBlockRange(newSyncStatus *eth.SyncStatus) (inclusiveBlockRange, EnqueueBlockAction) { + if newSyncStatus.HeadL1 == (eth.L1BlockRef{}) { + // empty sync status + return inclusiveBlockRange{}, ActionRetry + } + + if l.prevSyncStatus != nil && newSyncStatus.CurrentL1.Number < l.prevSyncStatus.CurrentL1.Number { + // Sequencer restarted and hasn't caught up yet + l.batcher.degradedLog.Warn(l.batcher.Log, "sequencerCurrentL1Reversed", "sequencer currentL1 reversed", "new currentL1", newSyncStatus.CurrentL1.Number, "previous currentL1", l.prevSyncStatus.CurrentL1.Number) + return inclusiveBlockRange{}, ActionRetry + } + l.batcher.degradedLog.Clear(l.batcher.Log, "sequencerCurrentL1Reversed", "sequencer currentL1 caught up") + + l.prevSyncStatus = newSyncStatus + + safeL2 := newSyncStatus.SafeL2 + + // State empty, just enqueue all unsafe blocks + if len(l.queuedBlocks) == 0 { + return inclusiveBlockRange{safeL2.Number + 1, newSyncStatus.UnsafeL2.Number}, ActionEnqueue + } + + lastQueuedBlock := l.queuedBlocks[len(l.queuedBlocks)-1] + firstQueuedBlock := l.queuedBlocks[0] + nextSafeBlockNum := safeL2.Number + 1 + + if lastQueuedBlock.Number >= newSyncStatus.UnsafeL2.Number { + // nothing to enqueue, unsafe block number is not higher than safe + return inclusiveBlockRange{}, ActionRetry + } + + if lastQueuedBlock.Number < safeL2.Number { + // derivation pipeline is somehow ahead of us, reset + return inclusiveBlockRange{}, ActionReset + } + + if nextSafeBlockNum < firstQueuedBlock.Number { + l.batcher.Log.Warn("next safe block is below oldest block in state") + return inclusiveBlockRange{}, ActionReset + } + + numBlocksToEnqueue := nextSafeBlockNum - firstQueuedBlock.Number + + if numBlocksToEnqueue > uint64(len(l.queuedBlocks)) { + l.batcher.Log.Warn("safe head above newest block in state, resetting loader") + return inclusiveBlockRange{}, ActionReset + } + + if numBlocksToEnqueue > 0 && l.queuedBlocks[numBlocksToEnqueue-1].Hash != safeL2.Hash { + l.batcher.Log.Warn("safe chain reorg, resetting loader") + return inclusiveBlockRange{}, ActionReset + } + + if safeL2.Number > firstQueuedBlock.Number { + numFinalizedBlocksInQueue := safeL2.Number - firstQueuedBlock.Number + l.batcher.Log.Warn( + "Removing finalized blocks from queued", + "numFinalizedBlocksInQueue", numFinalizedBlocksInQueue, + "safeL2", safeL2, + "firstQueuedBlock", firstQueuedBlock) + l.queuedBlocks = l.queuedBlocks[numFinalizedBlocksInQueue:] + } + + return inclusiveBlockRange{lastQueuedBlock.Number + 1, newSyncStatus.UnsafeL2.Number}, ActionEnqueue +} + +// numBlocks is a convenience method for inclusiveBlockRange that can be +// utilized to quickly determine how many blocks are being referenced within +// the range. +func (i inclusiveBlockRange) numBlocks() uint64 { + return 1 + i.end - i.start +} + +// LARGE_BLOCK_GAP_THRESHOLD is a threshold for the number of blocks that we +// consider to be a "large gap" when queueing blocks to Espresso. We're +// interested in being alerted when we're falling behind. +const LARGE_BLOCK_GAP_THRESHOLD = 30 * 60 + +// blockLoadingLoop +// - polls the sequencer, +// - queues unsafe blocks from the sequencer to Espresso +func (l *BatchSubmitter) espressoBatchQueueingLoop(ctx context.Context, wg *sync.WaitGroup) { + ticker := time.NewTicker(l.Config.PollInterval) + defer ticker.Stop() + defer wg.Done() + + loader := BlockLoader{ + batcher: l, + } + + // * + // * BEFORE we start: + // * - scan batchInbox from batchInbox.lastBackfilled + // * - enqueue all batches from batchInbox that are _by fallback batcher_ to Espresso + // * - wait for espresso queue to clear + // * - set lastBackfilled to block height of the last of such batches + // * + + for { + select { + case <-ticker.C: + newSyncStatus, err := l.getSyncStatus(ctx) + if err != nil { + l.degradedLog.Warn(l.Log, "syncStatusErr/espressoBatchQueueing", "Couldn't get sync status", "error", err) + continue + } + l.degradedLog.Clear(l.Log, "syncStatusErr/espressoBatchQueueing", "sync status fetch recovered") + + blocksToQueue, action := loader.nextBlockRange(newSyncStatus) + + // We add a check here to add visibility to us that we've exceeded + // a threshold. + if numBlocks := blocksToQueue.numBlocks(); numBlocks >= LARGE_BLOCK_GAP_THRESHOLD { + l.Log.Warn("Large gap of blocks to enqueue to Espresso detected", "numBlocks", numBlocks, "blocksToQueue", blocksToQueue) + } + + if action == ActionEnqueue { + numEnqueuedBlocksBefore := len(loader.queuedBlocks) + loader.EnqueueBlocks(ctx, blocksToQueue) + numEnqueuedBlocksAfter := len(loader.queuedBlocks) + + // This is a check to help us determine whether we're able to + // push through all of the blocks we've attempted to or not. + if enqueued := numEnqueuedBlocksAfter - numEnqueuedBlocksBefore; enqueued < int(blocksToQueue.numBlocks()) { + // We weren't able to submit all of the blocks to Espresso + // that we were attempting to. + // + // TODO: We should probably throttle a bit. + l.Log.Debug("Could not enqueue all blocks to Espresso", "enqueued", enqueued, "attempted", blocksToQueue.numBlocks()) + } + } else if action == ActionReset { + loader.reset() + } + + case <-ctx.Done(): + l.Log.Info("blockLoadingLoop returning") + return + } + } +} + +func (l *BatchSubmitter) fetchBlock(ctx context.Context, blockNumber uint64) (*types.Block, error) { + l2Client, err := l.EndpointProvider.EthClient(ctx) + if err != nil { + return nil, fmt.Errorf("getting L2 client: %w", err) + } + + cCtx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout) + defer cancel() + + block, err := l2Client.BlockByNumber(cCtx, new(big.Int).SetUint64(blockNumber)) + if err != nil { + return nil, fmt.Errorf("getting L2 block: %w", err) + } + + return block, nil +} + +// resolveTEEVerifierAddress queries the BatchAuthenticator contract to get the +// EspressoTEEVerifier address. +func (l *BatchSubmitter) resolveTEEVerifierAddress() error { + if l.RollupConfig.BatchAuthenticatorAddress == (common.Address{}) { + // If batcher authenticator address is nil, we will keep teeVerifierAddress to nil as well + return nil + } + auth, err := bindings.NewBatchAuthenticatorCaller(l.RollupConfig.BatchAuthenticatorAddress, l.L1Client) + if err != nil { + return fmt.Errorf("failed to create BatchAuthenticator caller: %w", err) + } + addr, err := auth.EspressoTEEVerifier(nil) + if err != nil { + return fmt.Errorf("failed to query EspressoTEEVerifier address: %w", err) + } + l.teeVerifierAddress = addr + l.Log.Info("Resolved TEE verifier address", "address", addr.Hex()) + return nil +} + +func (l *BatchSubmitter) registerBatcher(ctx context.Context) error { + if len(l.Espresso.Attestation) == 0 { + l.Log.Warn("Attestation is empty, skipping registration") + return nil + } + + if l.Config.Espresso.AttestationService == "" { + l.Log.Warn("EspressoAttestationServices is not set, skipping registration") + return nil + } + + l.Log.Info("Batch authenticator address", "value", l.RollupConfig.BatchAuthenticatorAddress) + code, err := l.L1Client.CodeAt(ctx, l.RollupConfig.BatchAuthenticatorAddress, nil) + if err != nil { + return fmt.Errorf("failed to check code at contract address: %w", err) + } + if len(code) == 0 { + return fmt.Errorf("no contract deployed at this address %w", err) + } + + abi, err := bindings.BatchAuthenticatorMetaData.GetAbi() + if err != nil { + return fmt.Errorf("failed to get Batch Authenticator ABI: %w", err) + } + + onchainProof, err := l.GenerateZKProof(ctx, l.Espresso.Attestation) + if err != nil { + l.Log.Error("failed to generate zk proof from nitro attestation", "err", err) + return fmt.Errorf("failed to generate zk proof from nitro attestation: %w", err) + } + + journalBytes, err := hex.DecodeString(stripHexPrefix(onchainProof.RawProof.Journal)) + if err != nil { + l.Log.Error("failed to decode journal hex string", "err", err) + return fmt.Errorf("failed to decode journal hex string: %w", err) + } + onchainProofBytes, err := hex.DecodeString(stripHexPrefix(onchainProof.OnchainProof)) + if err != nil { + l.Log.Error("failed to decode onchain proof hex string", "err", err) + return fmt.Errorf("failed to decode onchain proof hex string: %w", err) + } + log.Info("successfully generated zk proof from nitro attestation") + + txData, err := abi.Pack("registerSigner", journalBytes, onchainProofBytes) + if err != nil { + return fmt.Errorf("failed to create registerSigner transaction: %w", err) + } + + candidate := txmgr.TxCandidate{ + TxData: txData, + To: &l.RollupConfig.BatchAuthenticatorAddress, + } + + l.Log.Info("Registering batcher with the BatchAuthenticator contract") + _, err = l.Txmgr.Send(ctx, candidate) + if err != nil { + return fmt.Errorf("failed to send registerBatcher transaction: %w", err) + } + + l.Log.Info("Registered batcher with the BatchAuthenticator contract") + + return nil +} + +func (l *BatchSubmitter) GenerateZKProof(ctx context.Context, attestationBytes []byte) (*EspressoOnchainProof, error) { + attestationServiceURL := strings.TrimSuffix(l.Config.Espresso.AttestationService, "/") + url := attestationServiceURL + "/generate_proof" + request, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(attestationBytes)) + if err != nil { + return nil, err + } + + request.Header.Set("Content-Type", "application/octet-stream") + client := http.Client{ + Timeout: 5 * time.Minute, + } + res, err := client.Do(request) + if err != nil { + return nil, err + } + defer (func() { + _ = res.Body.Close() + })() + + if res.StatusCode != http.StatusOK { + return nil, fmt.Errorf("received non-200 response: %d", res.StatusCode) + } + + responseData, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + var zkProof EspressoOnchainProof + err = json.Unmarshal(responseData, &zkProof) + if err != nil { + return nil, err + } + + return &zkProof, nil +} + +// sendTxWithEspresso authenticates a batch transaction via the BatchAuthenticator contract using +// a TEE-attested EIP-712 signature, then sends the batch data to the BatchInbox address. Both txs +// are submitted through the ordered txmgr queue (auth first, batch second) so they are mined in +// submission order, as required under Holocene, and both stay under MaxPendingTransactions +// (queue.Send blocks when the queue is full). A watcher goroutine collects both receipts and +// emits a single synthetic receipt for the batch txData. +func (l *BatchSubmitter) sendTxWithEspresso(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { + transactionReference := txRef{id: txdata.ID(), isCancel: isCancel, isBlob: txdata.daType == DaTypeBlob, daType: txdata.daType, size: txdata.Len()} + l.Log.Debug("Sending Espresso-enabled L1 transaction", "txRef", transactionReference) + + commitment, err := computeCommitment(candidate) + if err != nil { + receiptsCh <- txmgr.TxReceipt[txRef]{ + ID: transactionReference, + Err: err, + } + return + } + l.Log.Debug("Computed batch commitment", "txRef", transactionReference, "commitment", hexutil.Encode(commitment[:])) + + signature, err := l.signEIP712Commitment(commitment) + if err != nil { + receiptsCh <- txmgr.TxReceipt[txRef]{ + ID: transactionReference, + Err: fmt.Errorf("failed to sign transaction: %w", err), + } + return + } + + l.Log.Debug("Signed transaction", "txRef", transactionReference, "commitment", hexutil.Encode(commitment[:]), "sig", hexutil.Encode(signature)) + + batchAuthenticatorAbi, err := bindings.BatchAuthenticatorMetaData.GetAbi() + if err != nil { + receiptsCh <- txmgr.TxReceipt[txRef]{ + ID: transactionReference, + Err: fmt.Errorf("failed to get batch authenticator ABI: %w", err), + } + return + } + + authenticateBatchCalldata, err := batchAuthenticatorAbi.Pack("authenticateBatchInfo", commitment, signature) + if err != nil { + receiptsCh <- txmgr.TxReceipt[txRef]{ + ID: transactionReference, + Err: fmt.Errorf("failed to pack authenticateBatch calldata: %w", err), + } + return + } + + verifyCandidate := txmgr.TxCandidate{ + TxData: authenticateBatchCalldata, + To: &l.RollupConfig.BatchAuthenticatorAddress, + } + + l.submitAuthenticatedBatch(transactionReference, verifyCandidate, candidate, queue, receiptsCh) +} + +// signEIP712Commitment creates an EIP-712 signature for the given commitment using the batcher's private key. +func (l *BatchSubmitter) signEIP712Commitment(commitment [32]byte) ([]byte, error) { + typedData := apitypes.TypedData{ + Types: apitypes.Types{ + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "EspressoTEEVerifier": []apitypes.Type{ + {Name: "commitment", Type: "bytes32"}, + }, + }, + PrimaryType: "EspressoTEEVerifier", + Domain: apitypes.TypedDataDomain{ + Name: "EspressoTEEVerifier", + Version: "1", + ChainId: (*math.HexOrDecimal256)(l.RollupConfig.L1ChainID), + VerifyingContract: l.teeVerifierAddress.String(), + }, + Message: map[string]interface{}{ + "commitment": commitment, + }, + } + // Calculate the hash using go-ethereum's EIP-712 implementation + hash, _, err := apitypes.TypedDataAndHash(typedData) + if err != nil { + return nil, fmt.Errorf("failed to calculate EIP-712 hash: %w", err) + } + + signature, err := crypto.Sign(hash, l.Config.Espresso.BatcherPrivateKey) + if err != nil { + return nil, fmt.Errorf("failed to sign EIP-712 hash: %w", err) + } + + // Normalize the recovery ID (v) from 0/1 to 27/28 for Solidity's ECDSA.recover + // See: https://github.com/ethereum/go-ethereum/issues/19751#issuecomment-504900739 + if signature[64] < 27 { + signature[64] += 27 + } + return signature, nil +} + +func stripHexPrefix(hexStr string) string { + if len(hexStr) >= 2 && hexStr[:2] == "0x" { + return hexStr[2:] + } + return hexStr +} diff --git a/op-batcher/batcher/espresso_active.go b/op-batcher/batcher/espresso_active.go new file mode 100644 index 00000000000..a66c6c38a75 --- /dev/null +++ b/op-batcher/batcher/espresso_active.go @@ -0,0 +1,106 @@ +package batcher + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/espresso/bindings" +) + +// isBatcherActive checks if the current batcher is the active one by querying +// the BatchAuthenticator contract. Returns true if this batcher instance should +// be publishing batches, false if it should stay idle. +// +// It applies two gates: +// 1. Mode: the contract's activeIsEspresso flag must match this node's role +// (Config.Espresso.Enabled). activeIsEspresso==true means the Espresso batcher +// is active; false means the fallback batcher is active. +// 2. Identity: once the mode matches, the configured sender key (Txmgr.From) must +// be the authorized batcher for that mode, otherwise every authenticateBatchInfo +// call reverts (Unauthorized{Espresso,Fallback}Batcher) and the batcher loops. +func (l *BatchSubmitter) isBatcherActive(ctx context.Context) (bool, error) { + // Check if contract code exists at the address + code, err := l.L1Client.CodeAt(ctx, l.RollupConfig.BatchAuthenticatorAddress, nil) + if err != nil { + return false, fmt.Errorf("failed to check code at BatchAuthenticator address: %w", err) + } + if len(code) == 0 { + return false, fmt.Errorf("no contract code at BatchAuthenticator address %s", l.RollupConfig.BatchAuthenticatorAddress.Hex()) + } + + batchAuthenticator, err := bindings.NewBatchAuthenticator(l.RollupConfig.BatchAuthenticatorAddress, l.L1Client) + if err != nil { + return false, fmt.Errorf("failed to create BatchAuthenticator binding: %w", err) + } + + cCtx, cancel := context.WithTimeout(ctx, l.Config.NetworkTimeout) + defer cancel() + + callOpts := &bind.CallOpts{Context: cCtx} + + activeIsEspresso, err := batchAuthenticator.ActiveIsEspresso(callOpts) + if err != nil { + return false, fmt.Errorf("failed to check activeIsEspresso: %w", err) + } + + batcherAddr := l.Txmgr.From() + + modeActive := (activeIsEspresso && l.Config.Espresso.Enabled) || + (!activeIsEspresso && !l.Config.Espresso.Enabled) + if !modeActive { + l.Log.Warn("Batcher is not the active batcher, skipping publish", + "batcherAddr", batcherAddr, + "activeIsEspresso", activeIsEspresso, + "EspressoEnabled", l.Config.Espresso.Enabled, + ) + return false, nil + } + + // Our mode is active; make sure our sender key is the authorized batcher for it, + // otherwise every publish reverts (Unauthorized*Batcher) in a loop. + var expected common.Address + if activeIsEspresso { + expected, err = batchAuthenticator.EspressoBatcher(callOpts) + if err != nil { + return false, fmt.Errorf("failed to read espressoBatcher: %w", err) + } + } else { + expected, err = l.fallbackBatcherAddr(batchAuthenticator, callOpts) + if err != nil { + return false, err + } + } + + if batcherAddr != expected { + l.Log.Warn("Configured batcher key is not the authorized batcher for the active mode, skipping publish", + "batcherAddr", batcherAddr, + "expected", expected, + "activeIsEspresso", activeIsEspresso, + ) + return false, nil + } + + return true, nil +} + +// fallbackBatcherAddr returns the address the BatchAuthenticator's fallback batcher +func (l *BatchSubmitter) fallbackBatcherAddr(batchAuthenticator *bindings.BatchAuthenticator, opts *bind.CallOpts) (common.Address, error) { + systemConfigAddr, err := batchAuthenticator.SystemConfig(opts) + if err != nil { + return common.Address{}, fmt.Errorf("failed to read systemConfig address: %w", err) + } + systemConfig, err := bindings.NewSystemConfigCaller(systemConfigAddr, l.L1Client) + if err != nil { + return common.Address{}, fmt.Errorf("failed to create SystemConfig binding: %w", err) + } + batcherHash, err := systemConfig.BatcherHash(opts) + if err != nil { + return common.Address{}, fmt.Errorf("failed to read batcherHash: %w", err) + } + // batcherHash stores the batcher address in its low 20 bytes, + // which is why we use bytes to address + return common.BytesToAddress(batcherHash[:]), nil +} diff --git a/op-batcher/batcher/fallback_auth.go b/op-batcher/batcher/espresso_auth.go similarity index 69% rename from op-batcher/batcher/fallback_auth.go rename to op-batcher/batcher/espresso_auth.go index 419fb68d36e..d3a24e1439a 100644 --- a/op-batcher/batcher/fallback_auth.go +++ b/op-batcher/batcher/espresso_auth.go @@ -43,13 +43,6 @@ func computeCommitment(candidate *txmgr.TxCandidate) ([32]byte, error) { // 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]) { transactionReference := newTxRef(txdata, isCancel) - // The auth tx shares the batch txdata's identity (so a failure requeues the right frames) - // but is always a calldata tx. Auth failures must carry its real type: an ErrAlreadyReserved - // receipt labeled with the batch's blob type would make cancelBlockingTx cancel the wrong - // pool, leaving the reserving tx stuck. - authReference := transactionReference - authReference.isBlob = false - authReference.daType = DaTypeCalldata l.Log.Debug("Sending fallback-authenticated L1 transaction", "txRef", transactionReference) commitment, err := computeCommitment(candidate) @@ -86,16 +79,41 @@ func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, ca To: &l.RollupConfig.BatchAuthenticatorAddress, } + l.submitAuthenticatedBatch(transactionReference, verifyCandidate, candidate, queue, receiptsCh) +} + +// submitAuthenticatedBatch submits an authenticateBatchInfo tx (verifyCandidate) followed by the +// batch inbox tx (candidate), then spawns a watcher (tracked by authGroup so the publishing loop +// drains it before closing receiptsCh) that validates both receipts and emits a single synthetic +// receipt for the batch txData. +// +// Both the TEE and fallback auth paths share this submission flow; they differ only in how the +// authenticateBatchInfo calldata in verifyCandidate is built (TEE-attested signature vs empty +// signature relying on the contract's msg.sender check). +// +// The auth leg must land at or before the batch leg (the verifier scans a lookback window ending +// at the batch tx's L1 block), so the auth tx must take the lower, earlier-mined nonce. Calldata +// pairs are pipelined via queue.SendPair (auth leg first); blob batches fall back to a serialized +// submission because geth's cross-pool account reservation cannot hold a calldata auth tx and a +// blob batch tx at once. +func (l *BatchSubmitter) submitAuthenticatedBatch(transactionReference txRef, verifyCandidate txmgr.TxCandidate, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { + // The auth tx shares the batch txdata's identity (so a failure requeues the right frames) + // but is always a calldata tx. Auth failures must carry its real type: an ErrAlreadyReserved + // receipt labeled with the batch's blob type would make cancelBlockingTx cancel the wrong + // pool, leaving the reserving tx stuck. + authReference := transactionReference + authReference.isBlob = false + authReference.daType = DaTypeCalldata + l.Log.Debug( - "Sending fallback authenticateBatchInfo transaction", + "Sending authenticateBatchInfo transaction", "txRef", transactionReference, - "commitment", hexutil.Encode(commitment[:]), "address", l.RollupConfig.BatchAuthenticatorAddress.String(), ) if len(candidate.Blobs) > 0 { // SendPair doesn't support blobs. - l.sendFallbackAuthSerialized(transactionReference, authReference, verifyCandidate, candidate, queue, receiptsCh) + l.sendAuthSerialized(transactionReference, authReference, verifyCandidate, candidate, queue, receiptsCh) return } @@ -112,19 +130,19 @@ func (l *BatchSubmitter) sendTxWithFallbackAuth(txdata txData, isCancel bool, ca l.authGroup.Add(1) go func() { defer l.authGroup.Done() - l.watchFallbackAuthReceipts(transactionReference, authReference, authReceiptCh, batchReceiptCh, receiptsCh) + l.watchAuthReceipts(transactionReference, authReference, authReceiptCh, batchReceiptCh, receiptsCh) }() } -// sendFallbackAuthSerialized submits an auth+batch pair serially: the auth tx is sent +// sendAuthSerialized submits an auth+batch pair serially: the auth tx is sent // and confirmed first, then the batch tx. It runs on the publishing-loop goroutine and // blocks it for the pair's full confirmation cycle, so consecutive pairs never overlap. -func (l *BatchSubmitter) sendFallbackAuthSerialized(transactionReference, authReference txRef, verifyCandidate txmgr.TxCandidate, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { +func (l *BatchSubmitter) sendAuthSerialized(transactionReference, authReference txRef, verifyCandidate txmgr.TxCandidate, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { authReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) queue.Send(authReference, verifyCandidate, authReceiptCh) authResult := <-authReceiptCh - if err := fallbackAuthResultError(authResult); err != nil { - l.Log.Error("Fallback authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) + if err := authResultError(authResult); err != nil { + l.Log.Error("authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) receiptsCh <- txmgr.TxReceipt[txRef]{ ID: authReference, Err: err, @@ -137,42 +155,42 @@ func (l *BatchSubmitter) sendFallbackAuthSerialized(transactionReference, authRe batchReceiptCh := make(chan txmgr.TxReceipt[txRef], 1) queue.Send(transactionReference, *candidate, batchReceiptCh) batchResult := <-batchReceiptCh - l.resolveFallbackAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) + l.resolveAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) } -// watchFallbackAuthReceipts collects the auth and batch receipts of a pipelined fallback-auth -// pair and resolves them into a single synthetic receipt (see resolveFallbackAuthPair). -func (l *BatchSubmitter) watchFallbackAuthReceipts(transactionReference, authReference txRef, authReceiptCh, batchReceiptCh chan txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { +// watchAuthReceipts collects the auth and batch receipts of a pipelined auth+batch +// pair and resolves them into a single synthetic receipt (see resolveAuthPair). +func (l *BatchSubmitter) watchAuthReceipts(transactionReference, authReference txRef, authReceiptCh, batchReceiptCh chan txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { authResult := <-authReceiptCh batchResult := <-batchReceiptCh - l.resolveFallbackAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) + l.resolveAuthPair(transactionReference, authReference, authResult, batchResult, receiptsCh) } -// fallbackAuthResultError interprets the auth leg's receipt: a send failure or a +// authResultError interprets the auth leg's receipt: a send failure or a // mined-but-reverted auth tx both fail the pair, since a reverted authenticateBatchInfo call // emits no BatchInfoAuthenticated event and the verifier would drop the batch (txmgr returns a // receipt as soon as the tx is mined, regardless of execution status). The batch inbox tx // needs no such status check: derivation reads its data by L1 inclusion, not by execution // status. -func fallbackAuthResultError(authResult txmgr.TxReceipt[txRef]) error { +func authResultError(authResult txmgr.TxReceipt[txRef]) error { if authResult.Err != nil { - return fmt.Errorf("failed to send fallback authenticateBatchInfo transaction: %w", authResult.Err) + return fmt.Errorf("failed to send authenticateBatchInfo transaction: %w", authResult.Err) } if authResult.Receipt.Status != types.ReceiptStatusSuccessful { - return fmt.Errorf("fallback authenticateBatchInfo transaction reverted: %s", authResult.Receipt.TxHash) + return fmt.Errorf("authenticateBatchInfo transaction reverted: %s", authResult.Receipt.TxHash) } return nil } -// resolveFallbackAuthPair validates the receipts of an auth+batch pair — auth success, batch +// resolveAuthPair validates the receipts of an auth+batch pair — auth success, batch // success, and that the batch tx landed within the lookback window of the auth tx — and // forwards a single synthetic receipt keyed to the batch txData onto receiptsCh. Any failure // produces an error receipt so the channel manager rewinds and resubmits the frame set. Auth -// failures are reported under authReference (see its construction in sendTxWithFallbackAuth +// failures are reported under authReference (see its construction in submitAuthenticatedBatch // for why the calldata typing matters). -func (l *BatchSubmitter) resolveFallbackAuthPair(transactionReference, authReference txRef, authResult, batchResult txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { - if err := fallbackAuthResultError(authResult); err != nil { - l.Log.Error("Fallback authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) +func (l *BatchSubmitter) resolveAuthPair(transactionReference, authReference txRef, authResult, batchResult txmgr.TxReceipt[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) { + if err := authResultError(authResult); err != nil { + l.Log.Error("authenticateBatchInfo transaction failed", "txRef", transactionReference, "err", err) receiptsCh <- txmgr.TxReceipt[txRef]{ ID: authReference, Err: err, diff --git a/op-batcher/batcher/fallback_auth_test.go b/op-batcher/batcher/espresso_auth_test.go similarity index 50% rename from op-batcher/batcher/fallback_auth_test.go rename to op-batcher/batcher/espresso_auth_test.go index 980c910bf0b..e3985bcc182 100644 --- a/op-batcher/batcher/fallback_auth_test.go +++ b/op-batcher/batcher/espresso_auth_test.go @@ -6,10 +6,14 @@ import ( "testing" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/signer/core/apitypes" "github.com/stretchr/testify/require" + "github.com/ethereum-optimism/optimism/espresso/bindings" "github.com/ethereum-optimism/optimism/op-batcher/metrics" "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -29,10 +33,9 @@ type recordedSend struct { } // fakeTxSender records queued transactions in order and immediately delivers a -// canned response (by index) on the receipt channel, mimicking the txmgr -// Queue, which forwards exactly one receipt per queued transaction. SendPair -// records its two legs as two consecutive sends consuming two consecutive -// canned responses. +// canned response (by index) on the receipt channel, mimicking the txmgr Queue, +// which forwards exactly one receipt per queued transaction. SendPair records +// its two legs as two consecutive sends consuming two consecutive responses. type fakeTxSender struct { sends []recordedSend pairCalls int @@ -57,6 +60,8 @@ func (f *fakeTxSender) SendPair(firstID txRef, first txmgr.TxCandidate, firstCh f.deliver(secondID, second, secondCh, true) } +// fallbackAuthMetricsSpy counts the auth failure metrics so tests can assert the +// right signal fired for each failure mode. type fallbackAuthMetricsSpy struct { metrics.Metricer windowExceeded int @@ -64,7 +69,7 @@ type fallbackAuthMetricsSpy struct { func (s *fallbackAuthMetricsSpy) RecordFallbackAuthWindowExceeded() { s.windowExceeded++ } -func newFallbackAuthSubmitter(t *testing.T) *BatchSubmitter { +func newAuthSubmitter(t *testing.T) *BatchSubmitter { l := &BatchSubmitter{} l.Log = testlog.Logger(t, log.LevelDebug) l.Metr = metrics.NoopMetrics @@ -74,12 +79,12 @@ func newFallbackAuthSubmitter(t *testing.T) *BatchSubmitter { return l } -func testFallbackTxData() txData { +func testAuthTxData() txData { return singleFrameTxData(frameData{data: []byte("frame-data")}) } -// testBlobCandidate returns a tx candidate carrying one (zero) blob, which -// routes sendTxWithFallbackAuth onto the serialized blob path. +// testBlobCandidate returns a tx candidate carrying one (zero) blob, which routes +// submitAuthenticatedBatch onto the serialized blob path. func testBlobCandidate() *txmgr.TxCandidate { return &txmgr.TxCandidate{Blobs: []*eth.Blob{{}}} } @@ -92,14 +97,24 @@ func revertedReceiptWithBlock(num int64) *types.Receipt { return &types.Receipt{BlockNumber: big.NewInt(num), Status: types.ReceiptStatusFailed} } -// TestFallbackAuth_OrderingAndSuccess verifies a calldata batch is submitted -// as a single pipelined pair — the auth tx first (so it takes the lower nonce -// and lands first, as Espresso requires), the batch tx second — and that a -// single success receipt for the batch txData is emitted when both txs land -// within the lookback window. -func TestFallbackAuth_OrderingAndSuccess(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// authTxRef builds the txRef the auth paths key their receipts under. +func authTxRef(txdata txData) txRef { + return txRef{id: txdata.ID(), isBlob: txdata.daType == DaTypeBlob, daType: txdata.daType, size: txdata.Len()} +} + +// The following suite drives submitAuthenticatedBatch / watchAuthReceipts directly. This is the +// flow shared by both the fallback and TEE auth paths (they differ only in how the auth calldata +// is built; see TestFallbackAuth_Calldata / TestEspressoAuth_Calldata for that distinction). + +// TestSubmitAuthenticatedBatch_OrderingAndSuccess verifies a calldata batch is submitted as a +// single pipelined pair — the auth tx first (so it takes the lower nonce and lands first, as +// required under Holocene), the batch tx second — and that a single success receipt for the batch +// txData is emitted when both txs land within the lookback window. +func TestSubmitAuthenticatedBatch_OrderingAndSuccess(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() + ref := authTxRef(txdata) + verifyCandidate := txmgr.TxCandidate{TxData: []byte("auth-calldata"), To: &l.RollupConfig.BatchAuthenticatorAddress} candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} queue := &fakeTxSender{ @@ -110,17 +125,16 @@ func TestFallbackAuth_OrderingAndSuccess(t *testing.T) { } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) - l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.submitAuthenticatedBatch(ref, verifyCandidate, candidate, queue, receiptsCh) + l.authGroup.Wait() require.Len(t, queue.sends, 2) require.Equal(t, 1, queue.pairCalls, "calldata pair must be submitted via SendPair") require.True(t, queue.sends[0].viaPair) require.True(t, queue.sends[1].viaPair) - // First leg must target the BatchAuthenticator (the auth tx), giving it the - // lower, earlier-mined nonce. - require.NotNil(t, queue.sends[0].candidate.To) - require.Equal(t, l.RollupConfig.BatchAuthenticatorAddress, *queue.sends[0].candidate.To) - // Second leg is the batch tx itself. + // First send is the auth tx, giving it the lower, earlier-mined nonce. + require.Equal(t, verifyCandidate.TxData, queue.sends[0].candidate.TxData) + // Second send is the batch tx itself. require.Equal(t, candidate.TxData, queue.sends[1].candidate.TxData) got := <-receiptsCh @@ -129,42 +143,52 @@ func TestFallbackAuth_OrderingAndSuccess(t *testing.T) { require.Equal(t, txdata.ID().String(), got.ID.id.String()) } -// TestFallbackAuth_AuthFailureRetried verifies that an auth-leg failure of a -// pipelined pair produces an error receipt keyed to the batch txData so the -// frames are re-queued. Both legs enter the queue back-to-back (pipelined); -// the tx manager cancels the batch leg when the auth leg fails, surfacing as -// an ErrPairLegCancelled response on the batch channel. -func TestFallbackAuth_AuthFailureRetried(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() - candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} +func TestSubmitAuthenticatedBatch_AuthSendFailureRetried(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() queue := &fakeTxSender{ responses: []txmgr.TxReceipt[txRef]{ - {Err: errSendFailed}, // auth leg fails + {Err: errSendFailed}, // auth fails to send {Err: txmgr.ErrPairLegCancelled}, // batch leg cancelled by the pair }, } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) - l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.submitAuthenticatedBatch(authTxRef(txdata), txmgr.TxCandidate{}, &txmgr.TxCandidate{}, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) require.Equal(t, txdata.ID().String(), got.ID.id.String()) - require.Len(t, queue.sends, 2) - require.Equal(t, 1, queue.pairCalls) } -// TestFallbackAuth_AuthRevertedRetried verifies that an authenticateBatchInfo tx -// that mines but reverts (no event emitted for the verifier) produces an error -// receipt so the frames are re-queued, rather than being confirmed as success. -// The tx manager treats a reverted auth leg as a pair failure and cancels the -// batch leg. -func TestFallbackAuth_AuthRevertedRetried(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() - candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} +func TestSubmitAuthenticatedBatch_BatchSendFailureRetried(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() + + queue := &fakeTxSender{ + responses: []txmgr.TxReceipt[txRef]{ + {Receipt: receiptWithBlock(100)}, // auth lands + {Err: errSendFailed}, // batch fails to send + }, + } + receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) + + l.submitAuthenticatedBatch(authTxRef(txdata), txmgr.TxCandidate{}, &txmgr.TxCandidate{}, queue, receiptsCh) + l.authGroup.Wait() + + got := <-receiptsCh + require.Error(t, got.Err) + require.Equal(t, txdata.ID().String(), got.ID.id.String()) +} + +// TestSubmitAuthenticatedBatch_AuthRevertedRetried verifies that an authenticateBatchInfo tx that +// mines but reverts (no event emitted for the verifier) produces an error receipt so the frames +// are re-queued, rather than being confirmed as success. +func TestSubmitAuthenticatedBatch_AuthRevertedRetried(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() queue := &fakeTxSender{ responses: []txmgr.TxReceipt[txRef]{ @@ -174,45 +198,74 @@ func TestFallbackAuth_AuthRevertedRetried(t *testing.T) { } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) - l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.submitAuthenticatedBatch(authTxRef(txdata), txmgr.TxCandidate{}, &txmgr.TxCandidate{}, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) require.Equal(t, txdata.ID().String(), got.ID.id.String()) - require.Len(t, queue.sends, 2) - require.Equal(t, 1, queue.pairCalls) } -// TestFallbackAuth_BatchFailureRetried verifies a batch-leg failure produces an -// error receipt keyed to the batch txData. -func TestFallbackAuth_BatchFailureRetried(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() - candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} +// TestSubmitAuthenticatedBatch_WindowViolationRetried verifies that a batch tx landing outside the +// lookback window of the auth tx produces an error receipt (so the channel manager rewinds and +// resubmits), rather than being confirmed, and records the window-exceeded metric. +func TestSubmitAuthenticatedBatch_WindowViolationRetried(t *testing.T) { + l := newAuthSubmitter(t) + metr := &fallbackAuthMetricsSpy{Metricer: metrics.NoopMetrics} + l.Metr = metr + txdata := testAuthTxData() + tooFar := int64(100 + derive.BatchAuthLookbackWindow + 1) queue := &fakeTxSender{ responses: []txmgr.TxReceipt[txRef]{ - {Receipt: receiptWithBlock(100)}, // auth lands - {Err: errSendFailed}, // batch fails + {Receipt: receiptWithBlock(100)}, // auth + {Receipt: receiptWithBlock(tooFar)}, // batch too far away }, } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) - l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.submitAuthenticatedBatch(authTxRef(txdata), txmgr.TxCandidate{}, &txmgr.TxCandidate{}, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) require.Equal(t, txdata.ID().String(), got.ID.id.String()) - require.Equal(t, 1, queue.pairCalls) + require.Equal(t, 1, metr.windowExceeded, "window violation should record the window-exceeded metric") } -// TestFallbackAuth_BlobSerialized verifies a blob batch takes the serialized -// path: the auth tx is sent alone (not as a pair) and the blob batch tx is -// only sent after the auth tx confirms, because geth's cross-pool account -// reservation cannot hold a calldata auth tx and a blob batch tx at once. -func TestFallbackAuth_BlobSerialized(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestSubmitAuthenticatedBatch_WindowBoundaryAccepted pins the inclusive bound of the +// lookback window: a batch landing exactly BatchAuthLookbackWindow blocks after the auth +// tx is still accepted by the verifier (CollectAuthenticatedBatches scans +// [batchBlock - BatchAuthLookbackWindow, batchBlock]), so the batcher must not +// re-queue it. +func TestSubmitAuthenticatedBatch_WindowBoundaryAccepted(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() + + boundary := int64(100 + derive.BatchAuthLookbackWindow) + queue := &fakeTxSender{ + responses: []txmgr.TxReceipt[txRef]{ + {Receipt: receiptWithBlock(100)}, // auth + {Receipt: receiptWithBlock(boundary)}, // batch at the exact edge of the window + }, + } + receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) + + l.submitAuthenticatedBatch(authTxRef(txdata), txmgr.TxCandidate{}, &txmgr.TxCandidate{}, queue, receiptsCh) + l.authGroup.Wait() + + got := <-receiptsCh + require.NoError(t, got.Err) + require.Equal(t, receiptWithBlock(boundary).BlockNumber, got.Receipt.BlockNumber) + require.Equal(t, txdata.ID().String(), got.ID.id.String()) +} + +// TestAuth_BlobSerialized verifies a blob batch takes the serialized path: the auth tx is sent +// alone (not as a pair) and the blob batch tx is only sent after the auth tx confirms, because +// geth's cross-pool account reservation cannot hold a calldata auth tx and a blob batch tx at once. +func TestAuth_BlobSerialized(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() txdata.daType = DaTypeBlob candidate := testBlobCandidate() @@ -225,6 +278,7 @@ func TestFallbackAuth_BlobSerialized(t *testing.T) { receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() require.Len(t, queue.sends, 2) require.Equal(t, 0, queue.pairCalls, "blob pair must not be pipelined") @@ -237,13 +291,12 @@ func TestFallbackAuth_BlobSerialized(t *testing.T) { require.Equal(t, txdata.ID().String(), got.ID.id.String()) } -// TestFallbackAuth_BlobAuthFailureGatesBatch verifies the serialized blob path -// never sends the batch tx when the auth tx fails: the batch would be crafted -// at the next nonce while the failed auth send resets the txmgr nonce, leaving -// a nonce gap (and an unauthenticated blob batch) behind. -func TestFallbackAuth_BlobAuthFailureGatesBatch(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestAuth_BlobAuthFailureGatesBatch verifies the serialized blob path never sends the batch tx +// when the auth tx fails: the batch would be crafted at the next nonce while the failed auth send +// resets the txmgr nonce, leaving a nonce gap (and an unauthenticated blob batch) behind. +func TestAuth_BlobAuthFailureGatesBatch(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() txdata.daType = DaTypeBlob candidate := testBlobCandidate() @@ -255,6 +308,7 @@ func TestFallbackAuth_BlobAuthFailureGatesBatch(t *testing.T) { receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) @@ -262,12 +316,12 @@ func TestFallbackAuth_BlobAuthFailureGatesBatch(t *testing.T) { require.Len(t, queue.sends, 1) } -// TestFallbackAuth_BlobAuthRevertGatesBatch is the revert variant: a mined but -// reverted auth emits no BatchInfoAuthenticated event, so the blob batch would -// be unverifiable and must not be submitted at all. -func TestFallbackAuth_BlobAuthRevertGatesBatch(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestAuth_BlobAuthRevertGatesBatch is the revert variant: a mined but reverted auth emits no +// BatchInfoAuthenticated event, so the blob batch would be unverifiable and must not be submitted +// at all. +func TestAuth_BlobAuthRevertGatesBatch(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() txdata.daType = DaTypeBlob candidate := testBlobCandidate() @@ -279,6 +333,7 @@ func TestFallbackAuth_BlobAuthRevertGatesBatch(t *testing.T) { receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) @@ -286,15 +341,14 @@ func TestFallbackAuth_BlobAuthRevertGatesBatch(t *testing.T) { require.Len(t, queue.sends, 1) } -// TestFallbackAuth_AuthFailureTxRefType verifies that an auth-tx failure is -// reported under a calldata-typed txRef even when the batch txdata is blob. -// The auth tx is always calldata; if its ErrAlreadyReserved failure were -// labeled with the batch's blob type, cancelBlockingTx would send a calldata -// cancel against a blobpool reservation, which is rejected the same way, -// looping forever without ever displacing the stuck blob tx. -func TestFallbackAuth_AuthFailureTxRefType(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestAuth_AuthFailureTxRefType verifies that an auth-tx failure is reported under a +// calldata-typed txRef even when the batch txdata is blob. The auth tx is always calldata; if its +// ErrAlreadyReserved failure were labeled with the batch's blob type, cancelBlockingTx would send +// a calldata cancel against a blobpool reservation, which is rejected the same way, looping +// forever without ever displacing the stuck blob tx. +func TestAuth_AuthFailureTxRefType(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() txdata.daType = DaTypeBlob candidate := testBlobCandidate() @@ -306,6 +360,7 @@ func TestFallbackAuth_AuthFailureTxRefType(t *testing.T) { receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) @@ -315,11 +370,11 @@ func TestFallbackAuth_AuthFailureTxRefType(t *testing.T) { require.Equal(t, DaTypeCalldata, got.ID.daType) } -// TestFallbackAuth_BatchFailureTxRefType verifies the converse: a batch-tx -// failure keeps the batch txdata's own type on the forwarded receipt. -func TestFallbackAuth_BatchFailureTxRefType(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestAuth_BatchFailureTxRefType verifies the converse: a batch-tx failure keeps the batch +// txdata's own type on the forwarded receipt. +func TestAuth_BatchFailureTxRefType(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() txdata.daType = DaTypeBlob candidate := testBlobCandidate() @@ -332,6 +387,7 @@ func TestFallbackAuth_BatchFailureTxRefType(t *testing.T) { receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() got := <-receiptsCh require.Error(t, got.Err) @@ -341,58 +397,124 @@ func TestFallbackAuth_BatchFailureTxRefType(t *testing.T) { require.Equal(t, DaTypeBlob, got.ID.daType) } -// TestFallbackAuth_WindowViolationRetried verifies that a batch tx landing -// outside the lookback window of the auth tx produces an error receipt (so the -// channel manager rewinds and resubmits), rather than being confirmed. -func TestFallbackAuth_WindowViolationRetried(t *testing.T) { - l := newFallbackAuthSubmitter(t) - metr := &fallbackAuthMetricsSpy{Metricer: metrics.NoopMetrics} - l.Metr = metr - txdata := testFallbackTxData() +// unpackAuthenticateBatchInfo decodes an authenticateBatchInfo(bytes32,bytes) calldata blob into +// its commitment and signature arguments. +func unpackAuthenticateBatchInfo(t *testing.T, calldata []byte) (commitment [32]byte, signature []byte) { + abi, err := bindings.BatchAuthenticatorMetaData.GetAbi() + require.NoError(t, err) + method, ok := abi.Methods["authenticateBatchInfo"] + require.True(t, ok) + require.Equal(t, method.ID, calldata[:4], "calldata is not an authenticateBatchInfo call") + + args, err := method.Inputs.Unpack(calldata[4:]) + require.NoError(t, err) + require.Len(t, args, 2) + return args[0].([32]byte), args[1].([]byte) +} + +// TestFallbackAuth_Calldata verifies the distinguishing behavior of the fallback path: the auth tx +// it submits is authenticateBatchInfo(commitment, emptySig), relying on the contract's msg.sender +// check rather than a signature. +func TestFallbackAuth_Calldata(t *testing.T) { + l := newAuthSubmitter(t) + txdata := testAuthTxData() candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} - tooFar := int64(100 + derive.BatchAuthLookbackWindow + 1) queue := &fakeTxSender{ responses: []txmgr.TxReceipt[txRef]{ - {Receipt: receiptWithBlock(100)}, // auth - {Receipt: receiptWithBlock(tooFar)}, // batch too far away + {Receipt: receiptWithBlock(100)}, + {Receipt: receiptWithBlock(101)}, }, } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() - got := <-receiptsCh - require.Error(t, got.Err) - require.Equal(t, txdata.ID().String(), got.ID.id.String()) - require.Equal(t, 1, metr.windowExceeded, "window violation should record the fallback_auth_window_exceeded metric") + require.Len(t, queue.sends, 2) + require.NotNil(t, queue.sends[0].candidate.To) + require.Equal(t, l.RollupConfig.BatchAuthenticatorAddress, *queue.sends[0].candidate.To) + + expectedCommitment, err := computeCommitment(candidate) + require.NoError(t, err) + commitment, signature := unpackAuthenticateBatchInfo(t, queue.sends[0].candidate.TxData) + require.Equal(t, expectedCommitment, commitment) + require.Empty(t, signature, "fallback path must send an empty signature") } -// TestFallbackAuth_WindowBoundaryAccepted pins the inclusive bound of the lookback -// window: a batch landing exactly BatchAuthLookbackWindow blocks after the auth tx is -// still accepted by the verifier (CollectAuthenticatedBatches scans -// [batchBlock - BatchAuthLookbackWindow, batchBlock]), so the batcher must not -// re-queue it. -func TestFallbackAuth_WindowBoundaryAccepted(t *testing.T) { - l := newFallbackAuthSubmitter(t) - txdata := testFallbackTxData() +// TestEspressoAuth_Calldata verifies the distinguishing behavior of the TEE path: the auth tx it +// submits is authenticateBatchInfo(commitment, sig) where sig is a 65-byte EIP-712 signature over +// the commitment that recovers to the batcher's key. +func TestEspressoAuth_Calldata(t *testing.T) { + key, err := crypto.GenerateKey() + require.NoError(t, err) + + l := newAuthSubmitter(t) + l.RollupConfig.L1ChainID = big.NewInt(1) + l.teeVerifierAddress = common.HexToAddress("0x00000000000000000000000000000000000000bb") + l.Config.Espresso.BatcherPrivateKey = key + + txdata := testAuthTxData() candidate := &txmgr.TxCandidate{TxData: []byte("batch-calldata")} - boundary := int64(100 + derive.BatchAuthLookbackWindow) queue := &fakeTxSender{ responses: []txmgr.TxReceipt[txRef]{ - {Receipt: receiptWithBlock(100)}, // auth - {Receipt: receiptWithBlock(boundary)}, // batch at the exact edge of the window + {Receipt: receiptWithBlock(100)}, + {Receipt: receiptWithBlock(101)}, }, } receiptsCh := make(chan txmgr.TxReceipt[txRef], 1) - l.sendTxWithFallbackAuth(txdata, false, candidate, queue, receiptsCh) + l.sendTxWithEspresso(txdata, false, candidate, queue, receiptsCh) + l.authGroup.Wait() - got := <-receiptsCh - require.NoError(t, got.Err) - require.Equal(t, receiptWithBlock(boundary).BlockNumber, got.Receipt.BlockNumber) - require.Equal(t, txdata.ID().String(), got.ID.id.String()) + require.Len(t, queue.sends, 2) + require.NotNil(t, queue.sends[0].candidate.To) + require.Equal(t, l.RollupConfig.BatchAuthenticatorAddress, *queue.sends[0].candidate.To) + + expectedCommitment, err := computeCommitment(candidate) + require.NoError(t, err) + commitment, signature := unpackAuthenticateBatchInfo(t, queue.sends[0].candidate.TxData) + require.Equal(t, expectedCommitment, commitment) + require.Len(t, signature, 65, "TEE path must send a 65-byte EIP-712 signature") + + // Reconstruct the EIP-712 digest the batcher signed and confirm the signature recovers to the + // batcher's key. + typedData := apitypes.TypedData{ + Types: apitypes.Types{ + "EIP712Domain": []apitypes.Type{ + {Name: "name", Type: "string"}, + {Name: "version", Type: "string"}, + {Name: "chainId", Type: "uint256"}, + {Name: "verifyingContract", Type: "address"}, + }, + "EspressoTEEVerifier": []apitypes.Type{ + {Name: "commitment", Type: "bytes32"}, + }, + }, + PrimaryType: "EspressoTEEVerifier", + Domain: apitypes.TypedDataDomain{ + Name: "EspressoTEEVerifier", + Version: "1", + ChainId: (*math.HexOrDecimal256)(l.RollupConfig.L1ChainID), + VerifyingContract: l.teeVerifierAddress.String(), + }, + Message: map[string]interface{}{ + "commitment": commitment, + }, + } + digest, _, err := apitypes.TypedDataAndHash(typedData) + require.NoError(t, err) + + // Denormalize v (27/28 -> 0/1) before recovering, undoing the Solidity-compat normalization. + recoverSig := make([]byte, 65) + copy(recoverSig, signature) + require.Contains(t, []byte{27, 28}, recoverSig[64]) + recoverSig[64] -= 27 + + recovered, err := crypto.SigToPub(digest, recoverSig) + require.NoError(t, err) + require.Equal(t, crypto.PubkeyToAddress(key.PublicKey), crypto.PubkeyToAddress(*recovered)) } // TestComputeCommitment_Parity locks the batcher's batch-commitment computation to diff --git a/op-batcher/batcher/espresso_driver.go b/op-batcher/batcher/espresso_driver.go new file mode 100644 index 00000000000..03ac3a52bb2 --- /dev/null +++ b/op-batcher/batcher/espresso_driver.go @@ -0,0 +1,171 @@ +package batcher + +import ( + "context" + "fmt" + "math/big" + + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" + espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" + espressoStreamers "github.com/EspressoSystems/espresso-streamers/op" + "github.com/EspressoSystems/espresso-streamers/op/derivation" + "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" + + "github.com/ethereum-optimism/optimism/op-service/bigs" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" + "github.com/ethereum-optimism/optimism/op-service/txmgr" +) + +// EspressoDriverSetup groups all TEE-batcher-specific runtime state plumbed +// from BatcherService into DriverSetup. Defined here to keep the upstream +// Optimism DriverSetup field block compact (see driver.go). +// +// All fields are nil/zero when --espresso.enabled is false except for the +// fallback batcher's ChainSigner/SequencerAddress, which are always populated +// by applyEspressoDriverSetup. +type EspressoDriverSetup struct { + Client *espressoClient.MultipleNodesClient + LightClient *espressoLightClient.LightclientCaller + ChainSigner opcrypto.ChainSigner + SequencerAddress common.Address + Attestation []byte +} + +// batcherL1Adapter wraps the batcher's L1Client to implement espresso.L1Client +// (HeaderHashByNumber + bind.ContractCaller). +type batcherL1Adapter struct { + L1Client L1Client +} + +func (a *batcherL1Adapter) HeaderHashByNumber(ctx context.Context, number *big.Int) (common.Hash, error) { + h, err := a.L1Client.HeaderByNumber(ctx, number) + if err != nil { + return common.Hash{}, err + } + return h.Hash(), nil +} + +func (a *batcherL1Adapter) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) { + return a.L1Client.CodeAt(ctx, contract, blockNumber) +} + +func (a *batcherL1Adapter) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) { + return a.L1Client.CallContract(ctx, call, blockNumber) +} + +// EspressoStreamer returns the Espresso batch streamer for use by the service and tests. +func (l *BatchSubmitter) EspressoStreamer() espressoStreamers.EspressoStreamer[derivation.EspressoBatch] { + return l.espressoStreamer +} + +// setupEspressoStreamer constructs the Espresso streamer (and its buffered +// wrapper) for a freshly-built BatchSubmitter. Called from NewBatchSubmitter +// only when --espresso.enabled is set; no-op otherwise. Panics on streamer +// construction failure to mirror the existing NewBatchSubmitter behavior. +func (l *BatchSubmitter) setupEspressoStreamer() { + if !l.Config.Espresso.Enabled { + return + } + l1Adapter := &batcherL1Adapter{L1Client: l.L1Client} + // Convert typed nil pointer to untyped nil interface to avoid typed-nil interface panic + // in confirmEspressoBlockHeight when EspressoLightClient is not configured. + var lightClientIface espressoStreamers.LightClientCallerInterface + if l.Espresso.LightClient != nil { + lightClientIface = l.Espresso.LightClient + } + unbufferedStreamer, err := espressoStreamers.NewEspressoStreamer( + bigs.Uint64Strict(l.RollupConfig.L2ChainID), + l1Adapter, + l1Adapter, + l.Espresso.Client, + lightClientIface, + l.Log, + derivation.CreateEspressoBatchUnmarshaler(), + l.Config.Espresso.CaffeinationHeightEspresso, + l.Config.Espresso.CaffeinationHeightL2, + l.RollupConfig.BatchAuthenticatorAddress, + false, + ) + if err != nil { + panic(fmt.Sprintf("failed to create Espresso streamer: %v", err)) + } + l.espressoStreamer = espressoStreamers.NewBufferedEspressoStreamer(unbufferedStreamer) + l.Log.Info("Streamer started", "streamer", l.espressoStreamer) +} + +// startEspressoLoops registers the batcher with the BatchAuthenticator +// contract, resolves the TEE verifier address, spawns the Espresso transaction +// submitter, and starts the four Espresso-specific batcher goroutines (in +// addition to the upstream receiptsLoop and publishingLoop). Replaces the +// upstream three-goroutine pattern when --espresso.enabled is set. +func (l *BatchSubmitter) startEspressoLoops(receiptsCh chan txmgr.TxReceipt[txRef], publishSignal chan pubInfo, unsafeBytesUpdated chan int64) error { + if err := l.registerBatcher(l.killCtx); err != nil { + return fmt.Errorf("could not register with BatchAuthenticator contract: %w", err) + } + + // Resolve the TEE verifier address from the BatchAuthenticator contract. + if err := l.resolveTEEVerifierAddress(); err != nil { + return fmt.Errorf("could not resolve TEE verifier address: %w", err) + } + + l.espressoSubmitter = NewEspressoTransactionSubmitter( + WithContext(l.shutdownCtx), + WithWaitGroup(l.wg), + WithEspressoClient(l.Espresso.Client), + WithVerifyReceiptMaxBlocks(l.Config.Espresso.VerifyReceiptMaxBlocks), + WithVerifyReceiptSafetyTimeout(l.Config.Espresso.VerifyReceiptSafetyTimeout), + WithVerifyReceiptRetryDelay(l.Config.Espresso.VerifyReceiptRetryDelay), + ) + l.espressoSubmitter.SpawnWorkers(4, 4) + l.espressoSubmitter.Start() + + l.wg.Add(4) + go l.receiptsLoop(l.wg, receiptsCh) // ranges over receiptsCh channel + go l.espressoBatchQueueingLoop(l.shutdownCtx, l.wg) + go l.espressoBatchLoadingLoop(l.shutdownCtx, l.wg, publishSignal, unsafeBytesUpdated) // sends on unsafeBytesUpdated (if throttling enabled) and publishSignal. Closes them both when done + go l.publishingLoop(l.killCtx, l.wg, receiptsCh, publishSignal) // ranges over publishSignal, spawns routines which send on receiptsCh. Closes receiptsCh when done. + return nil +} + +// shouldSkipPublishForActiveSeq returns true if publishStateToL1 should skip +// publishing because this batcher is not the on-chain "active" batcher +// (BatchAuthenticator.activeIsEspresso). The Espresso TEE batcher always +// honors the flag (it is fundamentally a post-fork actor); the fallback +// batcher honors it only once fallback auth is required (pre-fork it must run +// as a vanilla upstream Optimism batcher with no BatchAuthenticator coupling). +// Fails closed: if either gate cannot be evaluated, publishing is skipped for +// this tick and retried on the next. +func (l *BatchSubmitter) shouldSkipPublishForActiveSeq(ctx context.Context) bool { + if l.RollupConfig.BatchAuthenticatorAddress == (common.Address{}) { + return false + } + consultActiveFlag := l.Config.Espresso.Enabled + if !consultActiveFlag { + fallbackAuthRequired, err := l.isFallbackAuthRequired(ctx) + if err != nil { + l.Log.Warn("Failed to evaluate fallback-auth gate, skipping publish", "err", err) + return true + } + consultActiveFlag = fallbackAuthRequired + } + if !consultActiveFlag { + return false + } + isActive, err := l.isBatcherActive(ctx) + if err != nil { + l.Log.Warn("Failed to check if batcher is active, skipping publish", "err", err) + return true + } + return !isActive +} + +// resetEspressoStreamer resets the Espresso streamer when --espresso.enabled +// is set; no-op otherwise. Called from clearState alongside the upstream +// channel-manager reset so the streamer's view of "next batch" matches the +// freshly-cleared channel state. +func (l *BatchSubmitter) resetEspressoStreamer() { + if l.Config.Espresso.Enabled { + l.EspressoStreamer().Reset() + } +} diff --git a/op-batcher/batcher/espresso_fallback_gate.go b/op-batcher/batcher/espresso_fallback_gate.go index 1157950bca5..88e23e9e3cf 100644 --- a/op-batcher/batcher/espresso_fallback_gate.go +++ b/op-batcher/batcher/espresso_fallback_gate.go @@ -9,19 +9,28 @@ import ( "github.com/ethereum-optimism/optimism/op-service/txmgr" ) -// dispatchAuthenticatedSendTx routes sendTx through the fallback-batcher -// post-fork auth path, returning true when the tx has been handed off to -// authGroup. Returns false to mean "fall through to the upstream queue.Send -// path" — pre-fork operation and any cancel tx. +// dispatchAuthenticatedSendTx routes sendTx through the Espresso (TEE) auth +// path or the fallback-batcher post-fork auth path, returning true when the tx +// has been handed off to authGroup. Returns false to mean "fall through to +// the upstream queue.Send path" — pre-fork fallback batcher and any cancel tx. // -// The fallback batcher consults isFallbackAuthRequired to gate authentication +// The TEE batcher (Config.Espresso.Enabled == true) always authenticates. The +// fallback batcher consults isFallbackAuthRequired to gate authentication // behind the EspressoTime hardfork: pre-fork the verifier accepts plain // sender-authenticated batches, and the BatchAuthenticator contract is -// irrelevant. +// irrelevant; calling authenticateBatchInfo pre-fork would also revert against +// the default activeIsEspresso=true contract state. func (l *BatchSubmitter) dispatchAuthenticatedSendTx(txdata txData, isCancel bool, candidate *txmgr.TxCandidate, queue TxSender[txRef], receiptsCh chan txmgr.TxReceipt[txRef]) bool { if isCancel { return false } + // Espresso batcher: authenticate via BatchAuthenticator. sendTxWithEspresso runs on the + // caller's goroutine and submits through the ordered queue (submitAuthenticatedBatch spawns + // its own authGroup-tracked watcher), so it must not be wrapped in a separate goroutine. + if l.Config.Espresso.Enabled { + l.sendTxWithEspresso(txdata, isCancel, candidate, queue, receiptsCh) + return true + } fallbackAuthRequired, err := l.isFallbackAuthRequired(l.killCtx) if err != nil { receiptsCh <- txmgr.TxReceipt[txRef]{ @@ -58,8 +67,8 @@ func (l *BatchSubmitter) isFallbackAuthRequired(ctx context.Context) (bool, erro return l.RollupConfig.IsEspresso(tip.Time), nil } -// waitForAuthGroup blocks until all in-flight fallback-auth watcher goroutines -// have finished. publishingLoop calls it before closing receiptsCh: each watcher +// waitForAuthGroup blocks until all in-flight auth goroutines (fallback watchers +// or TEE submissions) have finished. publishingLoop calls it before closing receiptsCh: each watcher // is a sender on receiptsCh, so the receipts loop must still be draining // receiptsCh at this point or a watcher's final send would block forever. Each // watcher always terminates because the txmgr Queue emits exactly one receipt per diff --git a/op-batcher/batcher/espresso_fallback_gate_test.go b/op-batcher/batcher/espresso_fallback_gate_test.go index fc9453e42bd..e7234507eec 100644 --- a/op-batcher/batcher/espresso_fallback_gate_test.go +++ b/op-batcher/batcher/espresso_fallback_gate_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -16,7 +17,11 @@ import ( "github.com/ethereum-optimism/optimism/op-service/testlog" ) +// mockFixedTimeL1Client is an L1Client whose tip header carries a fixed +// timestamp. The embedded bind.ContractBackend satisfies the rest of the +// L1Client interface; only HeaderByNumber is exercised by the gate. type mockFixedTimeL1Client struct { + bind.ContractBackend time uint64 } diff --git a/op-batcher/batcher/espresso_helpers_test.go b/op-batcher/batcher/espresso_helpers_test.go new file mode 100644 index 00000000000..31118a3f80f --- /dev/null +++ b/op-batcher/batcher/espresso_helpers_test.go @@ -0,0 +1,263 @@ +package batcher_test + +import ( + "context" + "encoding/binary" + "encoding/json" + "errors" + "sync" + + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" + tagged_base64 "github.com/EspressoSystems/espresso-network/sdks/go/tagged-base64" + "github.com/EspressoSystems/espresso-network/sdks/go/types" + common "github.com/EspressoSystems/espresso-network/sdks/go/types/common" +) + +// ErrNotImplemented is a sentinel error used to indicate that a method +// was not implemented. +var ErrNotImplemented = errors.New("not implemented") + +// AlwaysFailingEspressoClient is a mock implementation of the EspressoClient +// interface that always returns an error for every method call. This is +// useful for testing error handling in the batcher without relying on a +// real Espresso client. +type AlwaysFailingEspressoClient struct{} + +// Compile time check to ensure adherence to EspressoClient interface +var _ espressoClient.EspressoClient = (*AlwaysFailingEspressoClient)(nil) + +func (*AlwaysFailingEspressoClient) FetchLatestBlockHeight(ctx context.Context) (uint64, error) { + return 0, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchHeaderByHeight(ctx context.Context, height uint64) (types.HeaderImpl, error) { + return types.HeaderImpl{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchRawHeaderByHeight(ctx context.Context, height uint64) (json.RawMessage, error) { + return json.RawMessage{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchHeadersByRange(ctx context.Context, from uint64, until uint64) ([]types.HeaderImpl, error) { + return []types.HeaderImpl{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchTransactionsInBlock(ctx context.Context, blockHeight uint64, namespace uint64) (espressoClient.TransactionsInBlock, error) { + return espressoClient.TransactionsInBlock{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchTransactionByHash(ctx context.Context, hash *types.TaggedBase64) (types.TransactionQueryData, error) { + return types.TransactionQueryData{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchVidCommonByHeight(ctx context.Context, blockHeight uint64) (types.VidCommon, error) { + return types.VidCommon{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchExplorerTransactionByHash(ctx context.Context, hash *types.TaggedBase64) (types.ExplorerTransactionQueryData, error) { + return types.ExplorerTransactionQueryData{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) StreamPayloads(ctx context.Context, height uint64) (espressoClient.Stream[types.PayloadQueryData], error) { + return nil, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) StreamTransactions(ctx context.Context, height uint64) (espressoClient.Stream[types.TransactionQueryData], error) { + return nil, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) StreamTransactionsInNamespace(ctx context.Context, height uint64, namespace uint64) (espressoClient.Stream[types.TransactionQueryData], error) { + return nil, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) FetchNamespaceTransactionsInRange(ctx context.Context, fromHeight uint64, toHeight uint64, namespace uint64) ([]types.NamespaceTransactionsRangeData, error) { + return []types.NamespaceTransactionsRangeData{}, ErrNotImplemented +} + +func (*AlwaysFailingEspressoClient) SubmitTransaction(ctx context.Context, tx common.Transaction) (*common.TaggedBase64, error) { + return nil, ErrNotImplemented +} + +// EspressoClientSwappableImplementation is as implementation of EspressoClient +// that is just a proxy. +// +// This allows it to be created and swapped easily as needed for testing. +type EspressoClientSwappableImplementation struct { + sync.RWMutex + espClient espressoClient.EspressoClient +} + +// Compile time check to ensure adherence to EspressoClient interface +var _ espressoClient.EspressoClient = (*EspressoClientSwappableImplementation)(nil) + +func (c *EspressoClientSwappableImplementation) SetEspressoClient(client espressoClient.EspressoClient) { + c.Lock() + defer c.Unlock() + + c.espClient = client +} + +func (c *EspressoClientSwappableImplementation) FetchLatestBlockHeight(ctx context.Context) (uint64, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchLatestBlockHeight(ctx) +} + +func (c *EspressoClientSwappableImplementation) FetchHeaderByHeight(ctx context.Context, height uint64) (types.HeaderImpl, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchHeaderByHeight(ctx, height) +} + +func (c *EspressoClientSwappableImplementation) FetchRawHeaderByHeight(ctx context.Context, height uint64) (json.RawMessage, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchRawHeaderByHeight(ctx, height) +} + +func (c *EspressoClientSwappableImplementation) FetchHeadersByRange(ctx context.Context, from uint64, until uint64) ([]types.HeaderImpl, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchHeadersByRange(ctx, from, until) +} + +func (c *EspressoClientSwappableImplementation) FetchTransactionsInBlock(ctx context.Context, blockHeight uint64, namespace uint64) (espressoClient.TransactionsInBlock, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchTransactionsInBlock(ctx, blockHeight, namespace) +} + +func (c *EspressoClientSwappableImplementation) FetchTransactionByHash(ctx context.Context, hash *types.TaggedBase64) (types.TransactionQueryData, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchTransactionByHash(ctx, hash) +} + +func (c *EspressoClientSwappableImplementation) FetchVidCommonByHeight(ctx context.Context, blockHeight uint64) (types.VidCommon, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchVidCommonByHeight(ctx, blockHeight) +} + +func (c *EspressoClientSwappableImplementation) FetchExplorerTransactionByHash(ctx context.Context, hash *types.TaggedBase64) (types.ExplorerTransactionQueryData, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchExplorerTransactionByHash(ctx, hash) +} + +func (c *EspressoClientSwappableImplementation) StreamPayloads(ctx context.Context, height uint64) (espressoClient.Stream[types.PayloadQueryData], error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.StreamPayloads(ctx, height) +} + +func (c *EspressoClientSwappableImplementation) StreamTransactions(ctx context.Context, height uint64) (espressoClient.Stream[types.TransactionQueryData], error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.StreamTransactions(ctx, height) +} + +func (c *EspressoClientSwappableImplementation) StreamTransactionsInNamespace(ctx context.Context, height uint64, namespace uint64) (espressoClient.Stream[types.TransactionQueryData], error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.StreamTransactionsInNamespace(ctx, height, namespace) +} + +func (c *EspressoClientSwappableImplementation) FetchNamespaceTransactionsInRange(ctx context.Context, fromHeight uint64, toHeight uint64, namespace uint64) ([]types.NamespaceTransactionsRangeData, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.FetchNamespaceTransactionsInRange(ctx, fromHeight, toHeight, namespace) +} + +func (c *EspressoClientSwappableImplementation) SubmitTransaction(ctx context.Context, tx common.Transaction) (*common.TaggedBase64, error) { + c.RLock() + defer c.RUnlock() + + return c.espClient.SubmitTransaction(ctx, tx) +} + +// FakeSubmissionSucceedingEspressoClient is a mock implementation of the +// EspressoClient for the explicit purposes of submitting transactions, and +// seeing their response. +type FakeSubmissionSucceedingEspressoClient struct { + sync.RWMutex + espressoClient.EspressoClient + txns map[string]common.Transaction +} + +// Compile time check to ensure adherence to EspressoClient interface +var _ espressoClient.EspressoClient = (*FakeSubmissionSucceedingEspressoClient)(nil) + +var ( + ErrNotInitialized = errors.New("not initialized") + ErrHashCannotBeNil = errors.New("hash cannot be nil") + ErrTransactionNotFound = errors.New("transaction not found") +) + +func (c *FakeSubmissionSucceedingEspressoClient) Init() { + c.Lock() + defer c.Unlock() + c.txns = make(map[string]common.Transaction) +} + +// FetchTransactionByHash simulates fetching a transaction by its hash. it +// looks up a transaction for the given hash, and returns the transaction +// if it is found. +func (c *FakeSubmissionSucceedingEspressoClient) FetchTransactionByHash(ctx context.Context, hash *types.TaggedBase64) (types.TransactionQueryData, error) { + c.RLock() + defer c.RUnlock() + if c.txns == nil { + return types.TransactionQueryData{}, ErrNotInitialized + } + + if hash == nil { + return types.TransactionQueryData{}, ErrHashCannotBeNil + } + + txn, found := c.txns[hash.String()] + if !found { + return types.TransactionQueryData{}, ErrTransactionNotFound + } + + // Just to simulate some processing on the transaction + height := binary.LittleEndian.Uint64(txn.Payload) + + return types.TransactionQueryData{ + Transaction: txn, + Hash: hash, + Index: 0, + Proof: json.RawMessage{}, + BlockHash: nil, + BlockHeight: height, + }, nil +} + +// SubmitTransaction simulates a successful transaction submission and stores +// it for future retrieval +func (c *FakeSubmissionSucceedingEspressoClient) SubmitTransaction(ctx context.Context, tx common.Transaction) (*common.TaggedBase64, error) { + c.Lock() + defer c.Unlock() + if c.txns == nil { + return nil, ErrNotInitialized + } + + hash, err := tagged_base64.New("TX", tx.Payload) + if err != nil { + return nil, err + } + + c.txns[hash.String()] = tx + return hash, nil +} diff --git a/op-batcher/batcher/espresso_service.go b/op-batcher/batcher/espresso_service.go new file mode 100644 index 00000000000..baf99c89e1e --- /dev/null +++ b/op-batcher/batcher/espresso_service.go @@ -0,0 +1,195 @@ +package batcher + +import ( + "crypto/ecdsa" + "fmt" + "time" + + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" + espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" + espressoStreamers "github.com/EspressoSystems/espresso-streamers/op" + "github.com/EspressoSystems/espresso-streamers/op/derivation" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + "github.com/hf/nitrite" + + "github.com/ethereum-optimism/optimism/op-batcher/enclave" + "github.com/ethereum-optimism/optimism/op-service/bigs" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" +) + +// EspressoBatcherConfig groups all Espresso-specific configuration the +// batcher consumes at runtime. It is embedded as a single field on +// BatcherConfig (see service.go) to keep the upstream Optimism +// BatcherConfig field block compact and minimize cherry-pick churn. +// +// Fields are populated from CLIConfig.Espresso (and a few RollupConfig +// fallbacks) by initEspresso below; ephemeral key material is generated +// by initKeyPair (TEE) or copied from CLIConfig.Espresso.TestingBatcherPrivateKey +// (devnet/test). +type EspressoBatcherConfig struct { + Enabled bool + PollInterval time.Duration + AttestationService string + CaffeinationHeightEspresso uint64 + // CaffeinationHeightL2 is the L2 batch position at which the Espresso + // streamer should start emitting batches. Operational parameter for + // restarting batchers mid-chain (e.g. after a fallback batcher event). + // When zero, the driver falls back to + // RollupConfig.EspressoOriginBatchPos(). + CaffeinationHeightL2 uint64 + + // Receipt verification tuning for the Espresso transaction submitter. + VerifyReceiptMaxBlocks uint64 + VerifyReceiptSafetyTimeout time.Duration + VerifyReceiptRetryDelay time.Duration + + // BatcherPublicKey/BatcherPrivateKey is the batcher's identity for the + // Espresso authentication path. In TEE deployments the private key is + // generated inside the enclave (initKeyPair) and the public key is + // attested to via Nitro Enclave PCR0; outside TEE (devnet/test), the + // configured TestingBatcherPrivateKey overrides them in initEspresso. + BatcherPublicKey *ecdsa.PublicKey + BatcherPrivateKey *ecdsa.PrivateKey +} + +// EspressoStreamer returns the Espresso batch streamer driven by this batcher. +func (bs *BatcherService) EspressoStreamer() espressoStreamers.EspressoStreamer[derivation.EspressoBatch] { + return bs.driver.espressoStreamer +} + +// initChainSigner asserts that the configured TxManager implements the +// ChainSigner interface and stores the embedded ChainSigner on the service. +// Espresso uses ChainSigner to sign batch authentication payloads sent to the +// BatchAuthenticator contract; the cast is required by every Espresso path. +func (bs *BatcherService) initChainSigner(cfg *CLIConfig) error { + if !cfg.Espresso.Enabled { + return nil + } + cast, castOk := bs.TxManager.(opcrypto.ChainSigner) + if !castOk { + return fmt.Errorf("tx manager does not implement ChainSigner") + } + bs.ChainSigner = cast + return nil +} + +// applyEspressoDriverSetup writes the Espresso-specific fields onto a +// DriverSetup populated with upstream-Optimism fields. Kept separate from the +// main initDriver struct literal so that the upstream block stays in upstream +// shape — minimizing cherry-pick churn when upstream renames or reorders +// fields. +func (bs *BatcherService) applyEspressoDriverSetup(ds *DriverSetup) { + ds.Espresso.SequencerAddress = bs.TxManager.From() + ds.Espresso.ChainSigner = bs.ChainSigner + ds.Espresso.Client = bs.EspressoClient + ds.Espresso.LightClient = bs.EspressoLightClient + ds.Espresso.Attestation = bs.Attestation +} + +// initKeyPair generates an ephemeral ECDSA key pair for the batcher's +// Espresso authentication path. In TEE deployments this key is attested +// to via Nitro Enclave PCR0; outside TEE (devnet/test), the configured +// TestingBatcherPrivateKey overrides this key in initEspresso. +func (bs *BatcherService) initKeyPair() error { + key, err := crypto.GenerateKey() + if err != nil { + return fmt.Errorf("failed to generate key pair for batcher: %w", err) + } + bs.Espresso.BatcherPrivateKey = key + bs.Espresso.BatcherPublicKey = &key.PublicKey + return nil +} + +// initEspresso configures the Espresso TEE-batcher integration on the +// BatcherService. When --espresso.enabled is false this is a no-op (the +// fallback batcher gets its own FallbackAuthLeadTime knob from +// BatcherConfig). When enabled, it wires up the Espresso query-service +// client, light client, ephemeral key pair, and Nitro Enclave attestation +// (if running in TEE). +func (bs *BatcherService) initEspresso(cfg *CLIConfig) error { + if !cfg.Espresso.Enabled { + return nil + } + + if cfg.Espresso.RollupL1URL == "" { + cfg.Espresso.RollupL1URL = cfg.L1EthRpc + } + + if cfg.Espresso.RollupL1URL != cfg.L1EthRpc { + log.Warn("Espresso Rollup L1 URL differs from batcher's L1EthRpc") + } + + if cfg.Espresso.L1URL == "" { + log.Warn("Espresso L1 URL not provided, using batcher's L1EthRpc") + cfg.Espresso.L1URL = cfg.L1EthRpc + } + if cfg.Espresso.Namespace == 0 { + log.Info("Using L2 chain ID as namespace by default") + cfg.Espresso.Namespace = bigs.Uint64Strict(bs.RollupConfig.L2ChainID) + } + if cfg.Espresso.BatchAuthenticatorAddr == (common.Address{}) { + cfg.Espresso.BatchAuthenticatorAddr = bs.RollupConfig.BatchAuthenticatorAddress + } + + if err := cfg.Espresso.Check(); err != nil { + return fmt.Errorf("invalid Espresso config: %w", err) + } + + bs.Espresso.Enabled = true + bs.Espresso.PollInterval = cfg.Espresso.PollInterval + bs.Espresso.AttestationService = cfg.Espresso.EspressoAttestationService + bs.Espresso.CaffeinationHeightEspresso = cfg.Espresso.CaffeinationHeightEspresso + bs.Espresso.CaffeinationHeightL2 = cfg.Espresso.CaffeinationHeightL2 + bs.Espresso.VerifyReceiptMaxBlocks = cfg.Espresso.VerifyReceiptMaxBlocks + bs.Espresso.VerifyReceiptSafetyTimeout = cfg.Espresso.VerifyReceiptSafetyTimeout + bs.Espresso.VerifyReceiptRetryDelay = cfg.Espresso.VerifyReceiptRetryDelay + + client, err := espressoClient.NewMultipleNodesClient(cfg.Espresso.QueryServiceURLs) + if err != nil { + return fmt.Errorf("failed to create Espresso client: %w", err) + } + bs.EspressoClient = client + + lightClient, err := espressoLightClient.NewLightclientCaller(cfg.Espresso.LightClientAddr, bs.L1Client) + if err != nil { + return fmt.Errorf("failed to create Espresso light client: %w", err) + } + bs.EspressoLightClient = lightClient + + if err := bs.initKeyPair(); err != nil { + return fmt.Errorf("failed to create key pair for batcher: %w", err) + } + + // try to generate attestationBytes on public key when start batcher + attestationBytes, err := enclave.AttestationWithPublicKey(bs.Espresso.BatcherPublicKey) + if err != nil { + bs.Log.Info("Not running in enclave, skipping attestation", "info", err) + + // Replace ephemeral keys with configured keys, as in devnet they'll be pre-approved for batching + privateKey := cfg.Espresso.TestingBatcherPrivateKey + if privateKey == nil { + return fmt.Errorf("when not running in enclave, testing batcher private key should be set") + } + + publicKey := privateKey.Public() + publicKeyECDSA, ok := publicKey.(*ecdsa.PublicKey) + if !ok { + return fmt.Errorf("error casting public key to ECDSA") + } + + bs.Espresso.BatcherPrivateKey = privateKey + bs.Espresso.BatcherPublicKey = publicKeyECDSA + } else { + // output length of attestation + bs.Log.Info("Successfully got attestation. Attestation length", "length", len(attestationBytes)) + _, err := nitrite.Verify(attestationBytes, nitrite.VerifyOptions{}) + if err != nil { + return fmt.Errorf("Couldn't verify attestation: %w", err) + } + bs.Attestation = attestationBytes + } + + return nil +} diff --git a/op-batcher/batcher/espresso_transaction_submitter_test.go b/op-batcher/batcher/espresso_transaction_submitter_test.go new file mode 100644 index 00000000000..1e65887ee74 --- /dev/null +++ b/op-batcher/batcher/espresso_transaction_submitter_test.go @@ -0,0 +1,179 @@ +package batcher_test + +import ( + "context" + "encoding/binary" + "testing" + "time" + + espressoCommon "github.com/EspressoSystems/espresso-network/sdks/go/types" + "github.com/ethereum-optimism/optimism/op-batcher/batcher" + "github.com/stretchr/testify/require" +) + +const NUMBER_OF_TRANSACTIONS_TO_SUBMIT = 1024 * 4 + +// TestEspressoTransactionSubmitterDeadlock is a test that is meant to test +// the underlying conditions in the espressoTransactionSubmitter that may +// result in a deadlock. +// +// The basic idea is to intentionally trigger the circumstances that can +// trigger the deadlock to occur in the espressoTransactionSubmitter. This +// test with this description **SHOULD** remain as a regression test. +// +// The specific suspected criteria for triggering a deadlock in the +// implementation of the espressoTransactionSubmitter as of 2026-04-23 is +// as follows: +// +// - Assume that the Espresso Service is not ever returning successfully +// - Assume we are receiving a consistent stream of of new blocks coming in +// +// If we have both of these criteria, it should be possible to fill up the +// underlying channels of `espressoTransactionSubmitter` for both Submission +// jobs, and verification jobs. +// +// The way we *should* be able to detect the deadlock is if a call to +// SubmitTransaction blocks. +// +// NOTE: After this has been fixed, it is unlikely that this will fail in the +// future. This will primarily be due to `SubmitTransaction` being modified +// to longer explicitly block. +func TestEspressoTransactionSubmitterDeadlock(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + submitter := batcher.NewEspressoTransactionSubmitter( + batcher.WithEspressoClient(new(AlwaysFailingEspressoClient)), + batcher.WithContext(ctx), + ) + + submitter.SpawnWorkers(4, 4) + submitter.Start() + + for i := 0; i < NUMBER_OF_TRANSACTIONS_TO_SUBMIT; i++ { + txn := espressoCommon.Transaction{ + Namespace: 1, + Payload: make([]byte, 8), + } + binary.LittleEndian.PutUint64(txn.Payload, uint64(i)) + + submitCtx, submitCancel := context.WithCancel(context.Background()) + go (func(cancel context.CancelFunc, txn espressoCommon.Transaction) { + // The error is irrelevant here: the test only observes whether the + // call returns (via cancel) to detect a deadlock. + _ = submitter.SubmitTransaction(&txn) + cancel() + })(submitCancel, txn) + + timeoutCtx, timeoutCancel := context.WithTimeout(context.Background(), 200*time.Millisecond) + + select { + case <-submitCtx.Done(): + // Things progressed without issue. + timeoutCancel() + submitCancel() + continue + case <-timeoutCtx.Done(): + // The call to SubmitTransaction did not return within the expected + // time frame. + + timeoutCancel() + submitCancel() + t.Fatalf("SubmitTransaction has blocked for longer than 200 milliseconds, on transaction %d, for error: %s\n", i, timeoutCtx.Err()) + } + } + + // If we've gotten here, we have passed without deadlocking. +} + +// TestEspressoTransactionSubmitterProgress is a test that is meant to test +// and verify the modified behavior of the espressoTransactionSubmitter after +// the behavior change to explicitly address the potential for the previous +// deadlock condition. +// +// The resolution is to target and limit the number of active inflight requests +// pending to be submitted and verified on Espresso at a given time. We target +// this behavior explicitly by being able to configure a maximum number of +// pending or "inflight" requests that are being waited on. By setting this +// limit, and being able to track things going through the pipeline, we can +// ensure that we continue to make progress, and put back pressure on the +// submitter themselves, so that they don't keep trying to submit new +// transactions if we are unable to effectively handle them at our current +// capacity. +// +// This test ensures that this workflow can be processed by first starting with +// a failing EspressoClient, and once we hit the threshold of having too +// many in flight requests, we swap the Client over to a succeeding client, +// and ensure that we're able to get through our pending backlog, and all new +// transactions to submit without stalling. +func TestEspressoTransactionSubmitterProgress(t *testing.T) { + ctx, cancel := context.WithCancel(t.Context()) + defer cancel() + failingClient := new(AlwaysFailingEspressoClient) + succeedingClient := new(FakeSubmissionSucceedingEspressoClient) + succeedingClient.Init() + succeedingClient.EspressoClient = failingClient + espClient := new(EspressoClientSwappableImplementation) + espClient.SetEspressoClient(failingClient) + submitter := batcher.NewEspressoTransactionSubmitter( + batcher.WithEspressoClient(espClient), + batcher.WithContext(ctx), + ) + + submitter.SpawnWorkers(4, 4) + submitter.Start() + + i := 0 + for ; i < NUMBER_OF_TRANSACTIONS_TO_SUBMIT; i++ { + txn := espressoCommon.Transaction{ + Namespace: 1, + Payload: make([]byte, 8), + } + binary.LittleEndian.PutUint64(txn.Payload, uint64(i)) + err := submitter.SubmitTransaction(&txn) + + if err == nil { + continue + } + + // We've triggered the initial condition, and we're no longer + if _, ok := err.(batcher.ErrTooManyInFlightRequests); ok { + // able to submit new transactions to the queue, as we've filled + // the in flight capacity. + // NOTE: we decrement `i` here, as we didn't successfully submit it. + i-- + break + } + + require.NoError(t, err, "unexpected error encountered while submit transaction has been called") + } + + // Now we trigger the EspressoClient to start working again. + // This will effectively simulate that the external network has + // no recovered. + + espClient.SetEspressoClient(succeedingClient) + + // We need to wait a little bit to give the submitter time to process + // some of its backlog. + + time.Sleep(10 * time.Millisecond) + + for ; i < NUMBER_OF_TRANSACTIONS_TO_SUBMIT; i++ { + txn := espressoCommon.Transaction{ + Namespace: 1, + Payload: make([]byte, 8), + } + binary.LittleEndian.PutUint64(txn.Payload, uint64(i)) + err := submitter.SubmitTransaction(&txn) + + if _, ok := err.(batcher.ErrTooManyInFlightRequests); ok { + // Slow down a bit, and decrement `i` so we try it again. + i-- + time.Sleep(10 * time.Millisecond) + continue + } + + // No further errors should occur + require.NoError(t, err, "unexpected error encountered while submit transaction has been called") + } +} diff --git a/op-batcher/batcher/service.go b/op-batcher/batcher/service.go index 921fd87ee69..1b5b92b9cc5 100644 --- a/op-batcher/batcher/service.go +++ b/op-batcher/batcher/service.go @@ -8,6 +8,8 @@ import ( "sync/atomic" "time" + espressoClient "github.com/EspressoSystems/espresso-network/sdks/go/client" + espressoLightClient "github.com/EspressoSystems/espresso-network/sdks/go/light-client" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" @@ -22,6 +24,7 @@ import ( "github.com/ethereum-optimism/optimism/op-node/rollup" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" "github.com/ethereum-optimism/optimism/op-service/cliapp" + opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto" "github.com/ethereum-optimism/optimism/op-service/dial" "github.com/ethereum-optimism/optimism/op-service/eth" "github.com/ethereum-optimism/optimism/op-service/httputil" @@ -52,6 +55,11 @@ type BatcherConfig struct { // For throttling DA. See CLIConfig in config.go for details on these parameters. ThrottleParams config.ThrottleParams + + // Espresso groups all TEE-batcher-specific configuration. Defined in + // espresso_service.go to keep the upstream Optimism field block compact. + // Zero-valued when --espresso.enabled=false. + Espresso EspressoBatcherConfig } // BatcherService represents a full batch-submitter instance and its resources, @@ -82,6 +90,14 @@ type BatcherService struct { stopped atomic.Bool NotSubmittingOnStart bool + + // Espresso runtime state. Defined in espresso_service.go to keep the + // upstream Optimism field block compact. EspressoClient and + // EspressoLightClient are nil when --espresso.enabled=false. + EspressoClient *espressoClient.MultipleNodesClient + EspressoLightClient *espressoLightClient.LightclientCaller + opcrypto.ChainSigner + Attestation []byte } type DriverSetupOption func(setup *DriverSetup) @@ -191,6 +207,9 @@ func (bs *BatcherService) initFromCLIConfig(ctx context.Context, closeApp contex if err := bs.initPProf(cfg); err != nil { return fmt.Errorf("failed to init profiling: %w", err) } + if err := bs.initEspresso(cfg); err != nil { + return fmt.Errorf("failed to init Espresso: %w", err) + } bs.initDriver(opts...) if err := bs.initRPCServer(cfg); err != nil { return fmt.Errorf("failed to start RPC server: %w", err) @@ -387,6 +406,9 @@ func (bs *BatcherService) initTxManager(_ context.Context, cfg *CLIConfig) error return err } bs.TxManager = txManager + if err := bs.initChainSigner(cfg); err != nil { + return err + } return nil } @@ -439,6 +461,7 @@ func (bs *BatcherService) initDriver(opts ...DriverSetupOption) { ChannelConfig: bs.ChannelConfig, AltDA: bs.AltDA, } + bs.applyEspressoDriverSetup(&ds) for _, opt := range opts { opt(&ds) } diff --git a/op-batcher/enclave/attestation.go b/op-batcher/enclave/attestation.go new file mode 100644 index 00000000000..26c4adf7205 --- /dev/null +++ b/op-batcher/enclave/attestation.go @@ -0,0 +1,47 @@ +package enclave + +import ( + "crypto/ecdsa" + "errors" + + "github.com/ethereum/go-ethereum/crypto" + "github.com/hf/nsm" + "github.com/hf/nsm/request" +) + +func attest(nonce, userData, publicKey []byte) ([]byte, error) { + sess, err := nsm.OpenDefaultSession() + if err != nil { + return nil, err + } + defer sess.Close() + + res, err := sess.Send(&request.Attestation{ + Nonce: nonce, + UserData: userData, + PublicKey: publicKey, + }) + if err != nil { + return nil, err + } + + if res.Error != "" { + return nil, errors.New(string(res.Error)) + } + + if res.Attestation == nil || res.Attestation.Document == nil { + return nil, errors.New("NSM device did not return an attestation") + } + + return res.Attestation.Document, nil +} + +func AttestationWithPublicKey(publicKey *ecdsa.PublicKey) ([]byte, error) { + // Use empty slices for nonce and publicKey when they're not needed + nonce := make([]byte, 0) + txData := make([]byte, 0) + publicKeyBytes := crypto.FromECDSAPub(publicKey) + + // Call the existing attest function with txData as userData + return attest(nonce, txData, publicKeyBytes) +} diff --git a/op-batcher/flags/flags.go b/op-batcher/flags/flags.go index f17faf14c52..15e75126a04 100644 --- a/op-batcher/flags/flags.go +++ b/op-batcher/flags/flags.go @@ -8,6 +8,7 @@ import ( "github.com/urfave/cli/v2" + "github.com/ethereum-optimism/optimism/espresso" altda "github.com/ethereum-optimism/optimism/op-alt-da" "github.com/ethereum-optimism/optimism/op-batcher/compressor" "github.com/ethereum-optimism/optimism/op-node/rollup/derive" @@ -198,6 +199,7 @@ func init() { optionalFlags = append(optionalFlags, oppprof.CLIFlags(EnvVarPrefix)...) optionalFlags = append(optionalFlags, txmgr.CLIFlagsWithBTO(EnvVarPrefix)...) optionalFlags = append(optionalFlags, altda.CLIFlags(EnvVarPrefix, "")...) + optionalFlags = append(optionalFlags, espresso.CLIFlags(EnvVarPrefix, "")...) Flags = append(requiredFlags, optionalFlags...) } diff --git a/op-service/crypto/espresso.go b/op-service/crypto/espresso.go new file mode 100644 index 00000000000..b61e4b6fff1 --- /dev/null +++ b/op-service/crypto/espresso.go @@ -0,0 +1,168 @@ +package crypto + +import ( + "bytes" + "context" + "crypto/ecdsa" + "errors" + "fmt" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" + + hdwallet "github.com/ethereum-optimism/go-ethereum-hdwallet" + opsigner "github.com/ethereum-optimism/optimism/op-service/signer" +) + +// ChainSignerFactory creates a SignerFn that is bound to a specific ChainID +type ChainSignerFactory func(chainID *big.Int, from common.Address) ChainSigner + +// ChainSigner is a generic interface for signing transactions or arbitrary data. +type ChainSigner interface { + + // SignTransaction signs a transaction with the given address. + SignTransaction(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) + + // Sign signs the hash of arbitrary data. + Sign(ctx context.Context, hash []byte) ([]byte, error) +} + +// clientSigner is a ChainSigner that utilizes a remote signer to perform +// Sign and SignTransaction +type clientSigner struct { + signerClient *opsigner.SignerClient + fromAddress common.Address + chainID *big.Int +} + +// Sign implements Signer. +func (c *clientSigner) Sign(ctx context.Context, data []byte) ([]byte, error) { + return c.signerClient.Sign(ctx, c.fromAddress, data) +} + +// VerifySignature verifies that the signature was produced by the expected address. +// data is the original message (e.g., txdata.CallData()) and signature is the result +// from eth_sign. +func Verify(data []byte, signature []byte, expected common.Address) error { + + pubKey, err := crypto.SigToPub(data, signature) + if err != nil { + return fmt.Errorf("failed to recover public key: %w", err) + } + + // Convert the ecdsa.PublicKey to an Address + address := crypto.PubkeyToAddress(*pubKey) + + // Ensure that the derived address matches the expected address. + if !bytes.Equal(address.Bytes(), expected.Bytes()) { + return fmt.Errorf("address mismatch: got %s, expected %s", address.Hex(), expected.Hex()) + } + + return nil +} + +// SignTransaction implements Signer. +func (c *clientSigner) SignTransaction(ctx context.Context, address common.Address, tx *types.Transaction) (*types.Transaction, error) { + if !bytes.Equal(address[:], c.fromAddress[:]) { + return nil, fmt.Errorf("attempting to sign for %s, expected %s: ", address, c.fromAddress) + } + return c.signerClient.SignTransaction(ctx, c.chainID, address, tx) +} + +var _ ChainSigner = &clientSigner{} + +// privateKeySigner is a ChainSigner that delegates to the stored +// functions for performing Sign and SignTransaction. In general these stored +// functions are expected to have access to a private key that is not +// explicitly stored within the structure itself. +type privateKeySigner struct { + chainID *big.Int + st bind.SignerFn + fromAddress common.Address + s func(common.Address, []byte) ([]byte, error) +} + +// Sign implements Signer. +func (p *privateKeySigner) Sign(ctx context.Context, hash []byte) ([]byte, error) { + return p.s(p.fromAddress, hash) +} + +// SignTransaction implements Signer. +func (p *privateKeySigner) SignTransaction(ctx context.Context, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + return p.st(addr, tx) +} + +var _ ChainSigner = &privateKeySigner{} + +// ChainSignerFactoryFromConfig considers three ways that signers are created & then creates single factory from those config options. +// It can either take a remote signer (via opsigner.CLIConfig) or it can be provided either a mnemonic + derivation path or a private key. +// It prefers the remote signer, then the mnemonic or private key (only one of which can be provided). +func ChainSignerFactoryFromConfig(l log.Logger, privateKey, mnemonic, hdPath string, signerConfig opsigner.CLIConfig) (ChainSignerFactory, common.Address, error) { + var signer ChainSignerFactory + var fromAddress common.Address + if signerConfig.Enabled() { + signerClient, err := opsigner.NewSignerClientFromConfig(l, signerConfig) + if err != nil { + l.Error("Unable to create Signer Client", "error", err) + return nil, common.Address{}, fmt.Errorf("failed to create the signer client: %w", err) + } + fromAddress = common.HexToAddress(signerConfig.Address) + signer = func(chainID *big.Int, _ common.Address) ChainSigner { + return &clientSigner{ + signerClient: signerClient, + fromAddress: fromAddress, + chainID: chainID, + } + } + } else { + var privKey *ecdsa.PrivateKey + var err error + + if privateKey != "" && mnemonic != "" { + return nil, common.Address{}, errors.New("cannot specify both a private key and a mnemonic") + } + if privateKey == "" { + // Parse l2output wallet private key and L2OO contract address. + wallet, err := hdwallet.NewFromMnemonic(mnemonic) + if err != nil { + return nil, common.Address{}, fmt.Errorf("failed to parse mnemonic: %w", err) + } + + privKey, err = wallet.PrivateKey(accounts.Account{ + URL: accounts.URL{ + Path: hdPath, + }, + }) + if err != nil { + return nil, common.Address{}, fmt.Errorf("failed to create a wallet: %w", err) + } + } else { + privKey, err = crypto.HexToECDSA(strings.TrimPrefix(privateKey, "0x")) + if err != nil { + return nil, common.Address{}, fmt.Errorf("failed to parse the private key: %w", err) + } + } + // we force the curve to Geth's instance, because Geth does an equality check in the nocgo version: + // https://github.com/ethereum/go-ethereum/blob/723b1e36ad6a9e998f06f74cc8b11d51635c6402/crypto/signature_nocgo.go#L82 + privKey.PublicKey.Curve = crypto.S256() + fromAddress = crypto.PubkeyToAddress(privKey.PublicKey) + signer = func(chainID *big.Int, from common.Address) ChainSigner { + s := PrivateKeySignerFn(privKey, chainID) + return &privateKeySigner{ + chainID: chainID, + st: s, + s: func(addr common.Address, hash []byte) ([]byte, error) { + return crypto.Sign(hash, privKey) + }, + } + } + } + + return signer, fromAddress, nil +} diff --git a/op-service/crypto/espresso_test.go b/op-service/crypto/espresso_test.go new file mode 100644 index 00000000000..8d42df96f8b --- /dev/null +++ b/op-service/crypto/espresso_test.go @@ -0,0 +1,134 @@ +package crypto + +import ( + "testing" + + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/ethereum-optimism/optimism/op-service/signer" + "github.com/ethereum-optimism/optimism/op-service/testlog" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/log" +) + +// should be run with CGO_ENABLED=0 + +func TestVerify(t *testing.T) { + // happy path + batcherSignature := []byte{ + 109, 206, 105, 108, 152, 110, 156, 111, 239, 153, 224, 182, 140, 49, 105, 120, + 153, 163, 162, 47, 119, 34, 68, 128, 118, 33, 143, 79, 101, 212, 75, 161, + 124, 77, 236, 159, 70, 167, 95, 51, 92, 127, 236, 253, 4, 211, 222, 117, + 54, 27, 214, 232, 135, 87, 33, 77, 16, 155, 164, 116, 220, 116, 31, 208, 1, + } + sequencerBatchesByte := []byte{ + 166, 136, 91, 55, 49, 112, 45, 166, + 46, 142, 74, 143, 88, 74, 196, 106, + 127, 104, 34, 244, 226, 186, 80, 251, + 169, 2, 246, 123, 21, 136, 210, 59, + } + + expected := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + err := Verify(sequencerBatchesByte, batcherSignature, expected) + require.NoError(t, err) + + // wrong length batcher signature + wrongLengthBatcherSignature := []byte{ + 1, + } + err = Verify(sequencerBatchesByte, wrongLengthBatcherSignature, expected) + // check it returns an correct error: address mismatch + require.Error(t, err) + require.Contains(t, err.Error(), "failed to recover public key: invalid signature length") + + // wrong batcher signature + wrongBatcherSignature := []byte{ + 1, 1, 1, 1, 152, 110, 156, 111, 239, 153, 224, 182, 140, 49, 105, 120, + 153, 163, 162, 47, 119, 34, 68, 128, 118, 33, 143, 79, 101, 212, 75, 161, + 124, 77, 236, 159, 70, 167, 95, 51, 92, 127, 236, 253, 4, 211, 222, 117, + 54, 27, 214, 232, 135, 87, 33, 77, 16, 155, 164, 116, 220, 116, 31, 208, 1, + } + err = Verify(sequencerBatchesByte, wrongBatcherSignature, expected) + // check it returns an correct error: address mismatch + require.Error(t, err) + require.Contains(t, err.Error(), "address mismatch") + + // wrong expected address + wrongExpected := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C9") + err = Verify(sequencerBatchesByte, batcherSignature, wrongExpected) + require.Error(t, err) + require.Contains(t, err.Error(), "address mismatch") + +} + +func TestChainSignerFactoryFromMnemonic(t *testing.T) { + mnemonic := "test test test test test test test test test test test junk" + hdPath := "m/44'/60'/0'/0/1" + testChainSignerSignTransaction(t, "", mnemonic, hdPath, signer.CLIConfig{}) + testChainSignerSign(t, "", mnemonic, hdPath, signer.CLIConfig{}) +} + +func TestChainSignerFactoryFromKey(t *testing.T) { + priv := "59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d" + testChainSignerSignTransaction(t, priv, "", "", signer.CLIConfig{}) + testChainSignerSign(t, priv, "", "", signer.CLIConfig{}) +} + +func testChainSignerSignTransaction(t *testing.T, priv, mnemonic, hdPath string, cfg signer.CLIConfig) { + logger := testlog.Logger(t, log.LevelDebug) + + factoryFn, addr, err := ChainSignerFactoryFromConfig(logger, priv, mnemonic, hdPath, cfg) + require.NoError(t, err) + expectedAddr := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + require.Equal(t, expectedAddr, addr) + chainID := big.NewInt(10) + chainSigner := factoryFn(chainID, addr) // for chain ID 10 + tx := types.NewTx(&types.DynamicFeeTx{ + ChainID: chainID, + Nonce: 0, + GasTipCap: big.NewInt(1), + GasFeeCap: big.NewInt(1), + Gas: 21000, + To: nil, + Value: big.NewInt(0), + Data: []byte("test"), + }) + signedTx, err := chainSigner.SignTransaction(context.Background(), addr, tx) + require.NoError(t, err) + gethSigner := types.LatestSignerForChainID(chainID) + sender, err := gethSigner.Sender(signedTx) + require.NoError(t, err) + require.Equal(t, expectedAddr, sender) +} + +func testChainSignerSign(t *testing.T, priv, mnemonic, hdPath string, cfg signer.CLIConfig) { + logger := testlog.Logger(t, log.LevelDebug) + + factoryFn, addr, err := ChainSignerFactoryFromConfig(logger, priv, mnemonic, hdPath, cfg) + require.NoError(t, err) + expectedAddr := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + require.Equal(t, expectedAddr, addr) + chainID := big.NewInt(10) + chainSigner := factoryFn(chainID, addr) // for chain ID 10 + + payload := []byte{0x01, 0x02, 0x03, 0x04} + hash := crypto.Keccak256(payload) + signed, err := chainSigner.Sign(context.Background(), hash) + require.NoError(t, err) + + // Recover the public key from the signature and hash + pubKey, err := crypto.SigToPub(hash, signed) + require.NoError(t, err) + + // Convert the ecdsa.PublicKey to an Address + address := crypto.PubkeyToAddress(*pubKey) + + // Ensure that the derived address matches the expected address. + require.Equal(t, expectedAddr, address) +} diff --git a/op-service/log/repeat_state.go b/op-service/log/repeat_state.go new file mode 100644 index 00000000000..2147a05fec6 --- /dev/null +++ b/op-service/log/repeat_state.go @@ -0,0 +1,91 @@ +package log + +import ( + "sync" + "time" + + "github.com/ethereum/go-ethereum/log" +) + +// RepeatStateReminderInterval is how often a long-running degraded state +// re-emits a Warn so operators don't lose visibility while the state persists. +const RepeatStateReminderInterval = 5 * time.Minute + +// RepeatStateLogger collapses warnings tied to a "degraded state" into a single +// log on entry, periodic reminders while the state persists, and a recovery +// log on exit. This avoids flooding the log debouncer when a tick-driven loop +// fires the same warning every poll interval. +// +// State is keyed by a free-form string supplied by the caller; entries with +// different keys are independent. Safe for concurrent use. +// +// Unlike DebouncingHandler (which operates at the slog.Handler level on a +// short time window), RepeatStateLogger is caller-driven: the caller signals +// state recovery explicitly via Clear, which lets it emit a recovery log a +// handler-level facility can't produce. +type RepeatStateLogger struct { + mu sync.Mutex + states map[string]*repeatStateEntry + clock func() time.Time +} + +type repeatStateEntry struct { + firstSeen time.Time + lastLogged time.Time + occurrences int +} + +func NewRepeatStateLogger() *RepeatStateLogger { + return &RepeatStateLogger{ + states: make(map[string]*repeatStateEntry), + clock: time.Now, + } +} + +// Warn reports an observation of a degraded state. The first observation since +// the most recent Clear (or first ever for the key) emits at warn level. +// Subsequent observations within RepeatStateReminderInterval are silently +// counted; once the interval has elapsed a single reminder warn is emitted +// with the cumulative occurrence count and the total duration since the state +// became active. +func (r *RepeatStateLogger) Warn(l log.Logger, key, msg string, ctx ...any) { + now := r.clock() + r.mu.Lock() + e, active := r.states[key] + if !active { + r.states[key] = &repeatStateEntry{firstSeen: now, lastLogged: now, occurrences: 1} + r.mu.Unlock() + l.Warn(msg, ctx...) + return + } + e.occurrences++ + if now.Sub(e.lastLogged) < RepeatStateReminderInterval { + r.mu.Unlock() + return + } + occurrences := e.occurrences + duration := now.Sub(e.firstSeen).Round(time.Second) + e.lastLogged = now + r.mu.Unlock() + + l.Warn(msg, append([]any{"occurrences", occurrences, "duration", duration}, ctx...)...) +} + +// Clear marks the named state as resolved. If the state was active a single +// info-level recovery log is emitted summarising the duration and total +// occurrences. Calling Clear when the state is not active is a no-op, so it is +// safe to call on every successful tick of the loop. +func (r *RepeatStateLogger) Clear(l log.Logger, key, recoveryMsg string, ctx ...any) { + r.mu.Lock() + e, active := r.states[key] + delete(r.states, key) + r.mu.Unlock() + if !active { + return + } + args := append([]any{ + "duration", r.clock().Sub(e.firstSeen).Round(time.Second), + "occurrences", e.occurrences, + }, ctx...) + l.Info(recoveryMsg, args...) +} diff --git a/op-service/log/repeat_state_test.go b/op-service/log/repeat_state_test.go new file mode 100644 index 00000000000..1d74dc2a7be --- /dev/null +++ b/op-service/log/repeat_state_test.go @@ -0,0 +1,234 @@ +package log + +import ( + "context" + "log/slog" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/ethereum/go-ethereum/log" +) + +// safeTestRecorder is a thread-safe slog.Handler that captures records for +// assertions in concurrent tests. +type safeTestRecorder struct { + mu sync.Mutex + records []slog.Record +} + +func (r *safeTestRecorder) Enabled(context.Context, slog.Level) bool { return true } + +func (r *safeTestRecorder) Handle(_ context.Context, rec slog.Record) error { + r.mu.Lock() + defer r.mu.Unlock() + r.records = append(r.records, rec) + return nil +} + +func (r *safeTestRecorder) WithAttrs([]slog.Attr) slog.Handler { return r } +func (r *safeTestRecorder) WithGroup(string) slog.Handler { return r } + +func (r *safeTestRecorder) GetRecords() []slog.Record { + r.mu.Lock() + defer r.mu.Unlock() + result := make([]slog.Record, len(r.records)) + copy(result, r.records) + return result +} + +func (r *safeTestRecorder) Len() int { + r.mu.Lock() + defer r.mu.Unlock() + return len(r.records) +} + +// fakeClock returns the time stored at *t, allowing the test to advance time +// deterministically without sleeping. +func fakeClock(now *time.Time) func() time.Time { + return func() time.Time { return *now } +} + +// matchingRecords returns records whose level and message match the given +// level and msg. Pass an empty msg to match any message. +func matchingRecords(records []slog.Record, level slog.Level, msg string) []slog.Record { + var out []slog.Record + for _, r := range records { + if r.Level != level { + continue + } + if msg != "" && r.Message != msg { + continue + } + out = append(out, r) + } + return out +} + +// attrValue extracts the value of the named attribute, or nil if absent. +func attrValue(r slog.Record, key string) any { + var v any + r.Attrs(func(a slog.Attr) bool { + if a.Key == key { + v = a.Value.Any() + return false + } + return true + }) + return v +} + +func newCapturingLogger() (log.Logger, *safeTestRecorder) { + rec := new(safeTestRecorder) + return log.NewLogger(rec), rec +} + +func TestRepeatStateLogger_FirstWarnEmitsThenSuppresses(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + r.Warn(lgr, "k1", "degraded", "err", "boom") + now = now.Add(1 * time.Second) + r.Warn(lgr, "k1", "degraded", "err", "boom") + now = now.Add(1 * time.Second) + r.Warn(lgr, "k1", "degraded", "err", "boom") + + warns := matchingRecords(rec.GetRecords(), slog.LevelWarn, "degraded") + require.Len(t, warns, 1, "only the first observation should emit a log") + + require.Equal(t, "boom", attrValue(warns[0], "err")) + require.Nil(t, attrValue(warns[0], "occurrences"), "first emission should not carry an occurrences count") +} + +func TestRepeatStateLogger_ReminderAfterInterval(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + // Initial emission. + r.Warn(lgr, "k1", "degraded") + + // 9 silent observations, every 30s. Cumulative 4m30s, still under the 5m threshold. + for i := 0; i < 9; i++ { + now = now.Add(30 * time.Second) + r.Warn(lgr, "k1", "degraded") + } + warns := matchingRecords(rec.GetRecords(), slog.LevelWarn, "degraded") + require.Len(t, warns, 1, "no reminder before the interval has elapsed") + + // Cross the threshold. + now = now.Add(31 * time.Second) + r.Warn(lgr, "k1", "degraded") + + warns = matchingRecords(rec.GetRecords(), slog.LevelWarn, "degraded") + require.Len(t, warns, 2, "reminder should fire once the interval has elapsed") + + // Reminder reports cumulative occurrences: 1 initial + 9 silent + 1 reminder = 11. + require.EqualValues(t, int64(11), attrValue(warns[1], "occurrences")) + // Duration since firstSeen is 9*30s + 31s = 5m1s, rounded to nearest second. + require.Equal(t, 5*time.Minute+1*time.Second, attrValue(warns[1], "duration")) +} + +func TestRepeatStateLogger_KeysAreIndependent(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + r.Warn(lgr, "k1", "first state") + r.Warn(lgr, "k2", "second state") + // Both keys are now active. Repeats for either should be suppressed. + r.Warn(lgr, "k1", "first state") + r.Warn(lgr, "k2", "second state") + + require.Len(t, matchingRecords(rec.GetRecords(), slog.LevelWarn, "first state"), 1) + require.Len(t, matchingRecords(rec.GetRecords(), slog.LevelWarn, "second state"), 1) + + // Clearing one key must not affect the other. + r.Clear(lgr, "k1", "first recovered") + r.Warn(lgr, "k2", "second state") // still suppressed + require.Len(t, matchingRecords(rec.GetRecords(), slog.LevelWarn, "second state"), 1) +} + +func TestRepeatStateLogger_ClearEmitsRecoveryWhenActive(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + r.Warn(lgr, "k1", "degraded") + now = now.Add(2 * time.Second) + r.Warn(lgr, "k1", "degraded") // suppressed, but increments totalOccurrences + now = now.Add(3 * time.Second) + r.Clear(lgr, "k1", "recovered", "extra", "ctx") + + infos := matchingRecords(rec.GetRecords(), slog.LevelInfo, "recovered") + require.Len(t, infos, 1) + require.Equal(t, 5*time.Second, attrValue(infos[0], "duration")) + require.EqualValues(t, int64(2), attrValue(infos[0], "occurrences")) + require.Equal(t, "ctx", attrValue(infos[0], "extra")) +} + +func TestRepeatStateLogger_ClearWhenInactiveIsNoop(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + r.Clear(lgr, "k1", "recovered") + + require.Empty(t, matchingRecords(rec.GetRecords(), slog.LevelInfo, "recovered")) +} + +func TestRepeatStateLogger_FreshAfterClear(t *testing.T) { + lgr, rec := newCapturingLogger() + now := time.Unix(0, 0) + r := NewRepeatStateLogger() + r.clock = fakeClock(&now) + + r.Warn(lgr, "k1", "degraded") + r.Warn(lgr, "k1", "degraded") // suppressed + r.Clear(lgr, "k1", "recovered") + + // After Clear, the next Warn should emit again as a fresh first observation. + r.Warn(lgr, "k1", "degraded") + + warns := matchingRecords(rec.GetRecords(), slog.LevelWarn, "degraded") + require.Len(t, warns, 2, "first Warn after Clear should emit") + require.Nil(t, attrValue(warns[1], "occurrences"), "fresh emission should not carry an occurrences count") +} + +func TestRepeatStateLogger_ConcurrentCallersDoNotRace(t *testing.T) { + lgr, _ := newCapturingLogger() + r := NewRepeatStateLogger() + + const goroutines = 32 + const callsPerG = 200 + + var wg sync.WaitGroup + wg.Add(goroutines) + var clears atomic.Int64 + for g := 0; g < goroutines; g++ { + go func(id int) { + defer wg.Done() + key := "k" + string(rune('a'+(id%4))) + for i := 0; i < callsPerG; i++ { + r.Warn(lgr, key, "degraded", "g", id) + if i%50 == 0 { + r.Clear(lgr, key, "recovered") + clears.Add(1) + } + } + }(g) + } + wg.Wait() + // No assertion on counts — this test exists to flush out races under -race + // and to assert the logger does not panic under concurrent Warn/Clear. + require.Greater(t, clears.Load(), int64(0)) +} diff --git a/op-service/signer/espresso.go b/op-service/signer/espresso.go new file mode 100644 index 00000000000..f5895de6473 --- /dev/null +++ b/op-service/signer/espresso.go @@ -0,0 +1,18 @@ +package signer + +import ( + "context" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Sign represents the interface for signing things via eth_sign. +func (s *SignerClient) Sign(ctx context.Context, address common.Address, data []byte) ([]byte, error) { + var result hexutil.Bytes + if err := s.client.CallContext(ctx, &result, "eth_sign", address, data); err != nil { + return nil, fmt.Errorf("eth_sign failed: %w", err) + } + return result, nil +}