diff --git a/core/block_validator.go b/core/block_validator.go index cd76f329bb7a..0f912ccba0c4 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -31,6 +31,7 @@ import ( "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/circuitcapacitychecker" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/trie" ) @@ -177,20 +178,13 @@ func (v *BlockValidator) ValidateSystemTxs(block *types.Block) error { stx := tx.AsSystemTx() found := false - for _, sender := range v.config.Scroll.SystemTx.Senders { - if stx.Sender == sender { - found = true - break - } - } - - if !found { + if stx.Sender != rcfg.SystemSenderAddress { return ErrUnknownSystemSigner } found = false for _, contract := range v.config.Scroll.SystemTx.Contracts { - if *stx.To == contract { + if stx.To == contract { found = true break } diff --git a/core/state_transition.go b/core/state_transition.go index 2c024f218154..d43830344a0a 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -273,6 +273,18 @@ func (st *StateTransition) buyGas() error { func (st *StateTransition) preCheck() error { if st.msg.IsSystemTx() { + // Make sure this transaction's nonce is correct. + stNonce := st.state.GetNonce(st.msg.From()) + if msgNonce := st.msg.Nonce(); stNonce < msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooHigh, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce > msgNonce { + return fmt.Errorf("%w: address %v, tx: %d state: %d", ErrNonceTooLow, + st.msg.From().Hex(), msgNonce, stNonce) + } else if stNonce+1 < stNonce { + return fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, + st.msg.From().Hex(), stNonce) + } // system tx gas is free and not accounted for. st.gas += st.msg.Gas() st.initialGas = st.msg.Gas() diff --git a/core/types/system_tx.go b/core/types/system_tx.go index 0418320399b7..d357987ce080 100644 --- a/core/types/system_tx.go +++ b/core/types/system_tx.go @@ -9,9 +9,9 @@ import ( ) type SystemTx struct { - Sender common.Address // pre-determined sender - To *common.Address // system contract - Data []byte // calldata + Sender common.Address // pre-determined sender + To common.Address // system contract address + Data []byte // calldata } // not accountend @@ -22,7 +22,7 @@ func (tx *SystemTx) txType() byte { return SystemTxType } func (tx *SystemTx) copy() TxData { return &SystemTx{ Sender: tx.Sender, - To: copyAddressPtr(tx.To), + To: tx.To, Data: common.CopyBytes(tx.Data), } } @@ -36,7 +36,7 @@ func (tx *SystemTx) gasTipCap() *big.Int { return new(big.Int) } func (tx *SystemTx) gasFeeCap() *big.Int { return new(big.Int) } func (tx *SystemTx) value() *big.Int { return new(big.Int) } func (tx *SystemTx) nonce() uint64 { return 0 } -func (tx *SystemTx) to() *common.Address { return tx.To } +func (tx *SystemTx) to() *common.Address { return &tx.To } func (tx *SystemTx) rawSignatureValues() (v, r, s *big.Int) { return new(big.Int), new(big.Int), new(big.Int) diff --git a/eth/backend.go b/eth/backend.go index 91bbc47e4ae8..d31a36935629 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -73,6 +73,7 @@ type Ethereum struct { txPool *core.TxPool syncService *sync_service.SyncService rollupSyncService *rollup_sync_service.RollupSyncService + l1Client sync_service.EthClient blockchain *core.BlockChain handler *handler ethDialCandidates enode.Iterator @@ -148,6 +149,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl } eth := &Ethereum{ config: config, + l1Client: l1Client, chainDb: chainDb, eventMux: stack.EventMux(), accountManager: stack.AccountManager(), @@ -218,7 +220,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl eth.txPool = core.NewTxPool(config.TxPool, chainConfig, eth.blockchain) // initialize and start L1 message sync service - eth.syncService, err = sync_service.NewSyncService(context.Background(), chainConfig, stack.Config(), eth.chainDb, l1Client) + eth.syncService, err = sync_service.NewSyncService(context.Background(), chainConfig, stack.Config(), eth.chainDb, eth.blockchain, l1Client) if err != nil { return nil, fmt.Errorf("cannot initialize L1 sync service: %w", err) } @@ -544,6 +546,7 @@ func (s *Ethereum) Synced() bool { return atomic.LoadU func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } func (s *Ethereum) SyncService() *sync_service.SyncService { return s.syncService } +func (s *Ethereum) L1Client() sync_service.EthClient { return s.l1Client } // Protocols returns all the currently configured // network protocols to start. diff --git a/miner/miner.go b/miner/miner.go index 1f3f9d0d7744..c695e159e376 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -43,6 +43,7 @@ type Backend interface { TxPool() *core.TxPool ChainDb() ethdb.Database SyncService() *sync_service.SyncService + L1Client() sync_service.EthClient } // Config is the configuration parameters of mining. @@ -75,6 +76,7 @@ type Miner struct { } func New(eth Backend, config *Config, chainConfig *params.ChainConfig, mux *event.TypeMux, engine consensus.Engine, isLocalBlock func(block *types.Block) bool) *Miner { + //system_contracts.NewL1BlocksWorker(context.Background(), ) miner := &Miner{ eth: eth, mux: mux, diff --git a/miner/miner_test.go b/miner/miner_test.go index a83fc2c8b1ec..835ae4895b0a 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -66,6 +66,10 @@ func (m *mockBackend) ChainDb() ethdb.Database { return m.chainDb } +func (m *mockBackend) L1Client() sync_service.EthClient { + return nil +} + type testBlockChain struct { statedb *state.StateDB gasLimit uint64 diff --git a/miner/worker.go b/miner/worker.go index 5c9dd530e1fb..8b5285b1fa6a 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -40,6 +40,7 @@ import ( "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/circuitcapacitychecker" "github.com/scroll-tech/go-ethereum/rollup/fees" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rollup/tracing" "github.com/scroll-tech/go-ethereum/trie" ) @@ -201,6 +202,8 @@ type worker struct { chainSideSub event.Subscription l1MsgsCh chan core.NewL1MsgsEvent l1MsgsSub event.Subscription + l1BlocksCh chan core.NewL1MsgsEvent + l1BlocksSub event.Subscription // Channels newWorkCh chan *newWorkReq @@ -231,9 +234,10 @@ type worker struct { snapshotState *state.StateDB // atomic status counters - running int32 // The indicator whether the consensus engine is running or not. - newTxs int32 // New arrival transaction count since last sealing work submitting. - newL1Msgs int32 // New arrival L1 message count since last sealing work submitting. + running int32 // The indicator whether the consensus engine is running or not. + newTxs int32 // New arrival transaction count since last sealing work submitting. + newL1Msgs int32 // New arrival L1 message count since last sealing work submitting. + newL1BlocksTxs int32 // New arrival L1Blocks tx count since last sealing work submitting. // noempty is the flag used to control whether the feature of pre-seal empty // block is enabled. The default value is false(pre-seal is enabled by default). @@ -271,6 +275,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), l1MsgsCh: make(chan core.NewL1MsgsEvent, txChanSize), + l1BlocksCh: make(chan core.NewL1MsgsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), @@ -298,6 +303,17 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus }) } + // Subscribe NewL1BlocksEvent from sync service + if s := eth.SyncService(); s != nil && chainConfig.Scroll.SystemTx.Enabled { + worker.l1BlocksSub = s.SubscribeNewL1BlocksTx(worker.l1BlocksCh) + } else { + // create an empty subscription so that the tests won't fail + worker.l1BlocksSub = event.NewSubscription(func(quit <-chan struct{}) error { + <-quit + return nil + }) + } + // Subscribe events for blockchain worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) @@ -463,6 +479,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { timer.Reset(recommit) atomic.StoreInt32(&w.newTxs, 0) atomic.StoreInt32(&w.newL1Msgs, 0) + atomic.StoreInt32(&w.newL1BlocksTxs, 0) } // clearPending cleans the stale pending tasks. clearPending := func(number uint64) { @@ -492,7 +509,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { // higher priced transactions. Disable this overhead for pending blocks. if w.isRunning() && (w.chainConfig.Clique == nil || w.chainConfig.Clique.Period > 0) { // Short circuit if no new transaction arrives. - if atomic.LoadInt32(&w.newTxs) == 0 && atomic.LoadInt32(&w.newL1Msgs) == 0 { + if atomic.LoadInt32(&w.newTxs) == 0 && atomic.LoadInt32(&w.newL1Msgs) == 0 && atomic.LoadInt32(&w.newL1BlocksTxs) == 0 { timer.Reset(recommit) continue } @@ -634,6 +651,9 @@ func (w *worker) mainLoop() { case ev := <-w.l1MsgsCh: atomic.AddInt32(&w.newL1Msgs, int32(ev.Count)) + case ev := <-w.l1BlocksCh: + atomic.AddInt32(&w.newL1BlocksTxs, int32(ev.Count)) + // System stopped case <-w.exitCh: return @@ -1331,8 +1351,8 @@ func (w *worker) collectPendingL1Messages(startIndex uint64) []types.L1MessageTx } func (w *worker) emitSystemTxs(env *environment) []*types.SystemTx { - // TODO - return nil + latestL1BlockNumberOnL2 := env.state.GetState(rcfg.L1BlocksAddress, rcfg.LatestBlockNumberSlot).Big().Uint64() + return w.eth.SyncService().CollectL1BlocksTxs(latestL1BlockNumberOnL2, w.chainConfig.Scroll.L1Config.MaxNumL1BlocksTxPerBlock) } // commitNewWork generates several new sealing tasks based on the parent block. @@ -1490,6 +1510,7 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) txs := types.NewOrderedSystemTxs(systemTxs) skipCommit, circuitCapacityReached = w.commitTransactions(txs, w.coinbase, interrupt) + // Todo: system txs should not be reverted. We probably need to handle if revert happens if skipCommit { l2CommitNewWorkCommitSystemTxTimer.UpdateSince(commitSystemTxStart) return diff --git a/miner/worker_test.go b/miner/worker_test.go index 7e3cb337b9dc..fc9c1cdb23c7 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -175,6 +175,7 @@ func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.c func (b *testWorkerBackend) TxPool() *core.TxPool { return b.txPool } func (b *testWorkerBackend) ChainDb() ethdb.Database { return b.db } func (b *testWorkerBackend) SyncService() *sync_service.SyncService { return nil } +func (b *testWorkerBackend) L1Client() sync_service.EthClient { return nil } func (b *testWorkerBackend) newRandomUncle() *types.Block { var parent *types.Block diff --git a/params/config.go b/params/config.go index 685d35ec6e0d..a058ef2caeba 100644 --- a/params/config.go +++ b/params/config.go @@ -367,10 +367,11 @@ var ( MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock, FeeVaultAddress: &rcfg.ScrollFeeVaultAddress, L1Config: &L1Config{ - L1ChainId: 1, - L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), - NumL1MessagesPerBlock: 10, - ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), + L1ChainId: 1, + L1MessageQueueAddress: common.HexToAddress("0x0d7E906BD9cAFa154b048cFa766Cc1E54E39AF9B"), + NumL1MessagesPerBlock: 10, + MaxNumL1BlocksTxPerBlock: 5, + ScrollChainAddress: common.HexToAddress("0xa13BAF47339d63B743e7Da8741db5456DAc1E556"), }, }, } @@ -386,7 +387,7 @@ var ( FeeVaultAddress: nil, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, - L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, }} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced @@ -400,7 +401,7 @@ var ( FeeVaultAddress: nil, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, - L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, }} TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), nil, false, big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(EthashConfig), nil, @@ -409,7 +410,7 @@ var ( FeeVaultAddress: &common.Address{123}, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, - L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, }} TestRules = TestChainConfig.Rules(new(big.Int)) @@ -419,7 +420,7 @@ var ( FeeVaultAddress: nil, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, - L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0, 0, common.HexToAddress("0x0000000000000000000000000000000000000000")}, }} ) @@ -540,16 +541,16 @@ type SystemTxConfig struct { Enabled bool `json:"enabled"` // TODO maybe move these in state (system contract) - Senders []common.Address `json:"senders"` Contracts []common.Address `json:"contracts"` } // L1Config contains the l1 parameters needed to sync l1 contract events (e.g., l1 messages, commit/revert/finalize batches) in the sequencer type L1Config struct { - L1ChainId uint64 `json:"l1ChainId,string,omitempty"` - L1MessageQueueAddress common.Address `json:"l1MessageQueueAddress,omitempty"` - NumL1MessagesPerBlock uint64 `json:"numL1MessagesPerBlock,string,omitempty"` - ScrollChainAddress common.Address `json:"scrollChainAddress,omitempty"` + L1ChainId uint64 `json:"l1ChainId,string,omitempty"` + L1MessageQueueAddress common.Address `json:"l1MessageQueueAddress,omitempty"` + NumL1MessagesPerBlock uint64 `json:"numL1MessagesPerBlock,string,omitempty"` + MaxNumL1BlocksTxPerBlock uint64 `json:"maxNumL1BlocksTxPerBlock,string,omitempty"` + ScrollChainAddress common.Address `json:"scrollChainAddress,omitempty"` } func (c *L1Config) String() string { diff --git a/rollup/abis/l1_blocks_abi.go b/rollup/abis/l1_blocks_abi.go new file mode 100644 index 000000000000..965d5a054626 --- /dev/null +++ b/rollup/abis/l1_blocks_abi.go @@ -0,0 +1,752 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package abis + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/scroll-tech/go-ethereum" + "github.com/scroll-tech/go-ethereum/accounts/abi" + "github.com/scroll-tech/go-ethereum/accounts/abi/bind" + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/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 +) + +// L1BlocksMetaData contains all meta data concerning the L1Blocks contract. +var L1BlocksMetaData = &bind.MetaData{ + ABI: "[{\"inputs\":[],\"name\":\"ErrorBlockUnavailable\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"blockHeight\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"blockTimestamp\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"baseFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"}],\"name\":\"ImportBlock\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlobBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getParentBeaconRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"name\":\"getStateRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestBlobBaseFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestBlockHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestBlockNumber\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestBlockTimestamp\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestParentBeaconRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestStateRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"blockHeaderRlp\",\"type\":\"bytes\"}],\"name\":\"setL1BlockHeader\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", +} + +// L1BlocksABI is the input ABI used to generate the binding from. +// Deprecated: Use L1BlocksMetaData.ABI instead. +var L1BlocksABI = L1BlocksMetaData.ABI + +// L1Blocks is an auto generated Go binding around an Ethereum contract. +type L1Blocks struct { + L1BlocksCaller // Read-only binding to the contract + L1BlocksTransactor // Write-only binding to the contract + L1BlocksFilterer // Log filterer for contract events +} + +// L1BlocksCaller is an auto generated read-only Go binding around an Ethereum contract. +type L1BlocksCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1BlocksTransactor is an auto generated write-only Go binding around an Ethereum contract. +type L1BlocksTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1BlocksFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type L1BlocksFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// L1BlocksSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type L1BlocksSession struct { + Contract *L1Blocks // 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 +} + +// L1BlocksCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type L1BlocksCallerSession struct { + Contract *L1BlocksCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// L1BlocksTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type L1BlocksTransactorSession struct { + Contract *L1BlocksTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// L1BlocksRaw is an auto generated low-level Go binding around an Ethereum contract. +type L1BlocksRaw struct { + Contract *L1Blocks // Generic contract binding to access the raw methods on +} + +// L1BlocksCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type L1BlocksCallerRaw struct { + Contract *L1BlocksCaller // Generic read-only contract binding to access the raw methods on +} + +// L1BlocksTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type L1BlocksTransactorRaw struct { + Contract *L1BlocksTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewL1Blocks creates a new instance of L1Blocks, bound to a specific deployed contract. +func NewL1Blocks(address common.Address, backend bind.ContractBackend) (*L1Blocks, error) { + contract, err := bindL1Blocks(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &L1Blocks{L1BlocksCaller: L1BlocksCaller{contract: contract}, L1BlocksTransactor: L1BlocksTransactor{contract: contract}, L1BlocksFilterer: L1BlocksFilterer{contract: contract}}, nil +} + +// NewL1BlocksCaller creates a new read-only instance of L1Blocks, bound to a specific deployed contract. +func NewL1BlocksCaller(address common.Address, caller bind.ContractCaller) (*L1BlocksCaller, error) { + contract, err := bindL1Blocks(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &L1BlocksCaller{contract: contract}, nil +} + +// NewL1BlocksTransactor creates a new write-only instance of L1Blocks, bound to a specific deployed contract. +func NewL1BlocksTransactor(address common.Address, transactor bind.ContractTransactor) (*L1BlocksTransactor, error) { + contract, err := bindL1Blocks(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &L1BlocksTransactor{contract: contract}, nil +} + +// NewL1BlocksFilterer creates a new log filterer instance of L1Blocks, bound to a specific deployed contract. +func NewL1BlocksFilterer(address common.Address, filterer bind.ContractFilterer) (*L1BlocksFilterer, error) { + contract, err := bindL1Blocks(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &L1BlocksFilterer{contract: contract}, nil +} + +// bindL1Blocks binds a generic wrapper to an already deployed contract. +func bindL1Blocks(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(L1BlocksABI)) + 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 (_L1Blocks *L1BlocksRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L1Blocks.Contract.L1BlocksCaller.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 (_L1Blocks *L1BlocksRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L1Blocks.Contract.L1BlocksTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L1Blocks *L1BlocksRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L1Blocks.Contract.L1BlocksTransactor.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 (_L1Blocks *L1BlocksCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _L1Blocks.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 (_L1Blocks *L1BlocksTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _L1Blocks.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_L1Blocks *L1BlocksTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _L1Blocks.Contract.contract.Transact(opts, method, params...) +} + +// GetBaseFee is a free data retrieval call binding the contract method 0x6c8af435. +// +// Solidity: function getBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCaller) GetBaseFee(opts *bind.CallOpts, blockNumber *big.Int) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getBaseFee", blockNumber) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetBaseFee is a free data retrieval call binding the contract method 0x6c8af435. +// +// Solidity: function getBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksSession) GetBaseFee(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBaseFee(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBaseFee is a free data retrieval call binding the contract method 0x6c8af435. +// +// Solidity: function getBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) GetBaseFee(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBaseFee(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlobBaseFee is a free data retrieval call binding the contract method 0x7e96ce1c. +// +// Solidity: function getBlobBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCaller) GetBlobBaseFee(opts *bind.CallOpts, blockNumber *big.Int) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getBlobBaseFee", blockNumber) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetBlobBaseFee is a free data retrieval call binding the contract method 0x7e96ce1c. +// +// Solidity: function getBlobBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksSession) GetBlobBaseFee(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBlobBaseFee(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlobBaseFee is a free data retrieval call binding the contract method 0x7e96ce1c. +// +// Solidity: function getBlobBaseFee(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) GetBlobBaseFee(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBlobBaseFee(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlockHash is a free data retrieval call binding the contract method 0xee82ac5e. +// +// Solidity: function getBlockHash(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) GetBlockHash(opts *bind.CallOpts, blockNumber *big.Int) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getBlockHash", blockNumber) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetBlockHash is a free data retrieval call binding the contract method 0xee82ac5e. +// +// Solidity: function getBlockHash(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksSession) GetBlockHash(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetBlockHash(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlockHash is a free data retrieval call binding the contract method 0xee82ac5e. +// +// Solidity: function getBlockHash(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) GetBlockHash(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetBlockHash(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlockTimestamp is a free data retrieval call binding the contract method 0x47e26f1a. +// +// Solidity: function getBlockTimestamp(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCaller) GetBlockTimestamp(opts *bind.CallOpts, blockNumber *big.Int) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getBlockTimestamp", blockNumber) + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// GetBlockTimestamp is a free data retrieval call binding the contract method 0x47e26f1a. +// +// Solidity: function getBlockTimestamp(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksSession) GetBlockTimestamp(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBlockTimestamp(&_L1Blocks.CallOpts, blockNumber) +} + +// GetBlockTimestamp is a free data retrieval call binding the contract method 0x47e26f1a. +// +// Solidity: function getBlockTimestamp(uint256 blockNumber) view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) GetBlockTimestamp(blockNumber *big.Int) (*big.Int, error) { + return _L1Blocks.Contract.GetBlockTimestamp(&_L1Blocks.CallOpts, blockNumber) +} + +// GetParentBeaconRoot is a free data retrieval call binding the contract method 0x78d8f5fe. +// +// Solidity: function getParentBeaconRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) GetParentBeaconRoot(opts *bind.CallOpts, blockNumber *big.Int) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getParentBeaconRoot", blockNumber) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetParentBeaconRoot is a free data retrieval call binding the contract method 0x78d8f5fe. +// +// Solidity: function getParentBeaconRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksSession) GetParentBeaconRoot(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetParentBeaconRoot(&_L1Blocks.CallOpts, blockNumber) +} + +// GetParentBeaconRoot is a free data retrieval call binding the contract method 0x78d8f5fe. +// +// Solidity: function getParentBeaconRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) GetParentBeaconRoot(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetParentBeaconRoot(&_L1Blocks.CallOpts, blockNumber) +} + +// GetStateRoot is a free data retrieval call binding the contract method 0xc3801938. +// +// Solidity: function getStateRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) GetStateRoot(opts *bind.CallOpts, blockNumber *big.Int) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "getStateRoot", blockNumber) + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// GetStateRoot is a free data retrieval call binding the contract method 0xc3801938. +// +// Solidity: function getStateRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksSession) GetStateRoot(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetStateRoot(&_L1Blocks.CallOpts, blockNumber) +} + +// GetStateRoot is a free data retrieval call binding the contract method 0xc3801938. +// +// Solidity: function getStateRoot(uint256 blockNumber) view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) GetStateRoot(blockNumber *big.Int) ([32]byte, error) { + return _L1Blocks.Contract.GetStateRoot(&_L1Blocks.CallOpts, blockNumber) +} + +// LatestBaseFee is a free data retrieval call binding the contract method 0x0385f4f1. +// +// Solidity: function latestBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksCaller) LatestBaseFee(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestBaseFee") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// LatestBaseFee is a free data retrieval call binding the contract method 0x0385f4f1. +// +// Solidity: function latestBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksSession) LatestBaseFee() (*big.Int, error) { + return _L1Blocks.Contract.LatestBaseFee(&_L1Blocks.CallOpts) +} + +// LatestBaseFee is a free data retrieval call binding the contract method 0x0385f4f1. +// +// Solidity: function latestBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) LatestBaseFee() (*big.Int, error) { + return _L1Blocks.Contract.LatestBaseFee(&_L1Blocks.CallOpts) +} + +// LatestBlobBaseFee is a free data retrieval call binding the contract method 0x6146da50. +// +// Solidity: function latestBlobBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksCaller) LatestBlobBaseFee(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestBlobBaseFee") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// LatestBlobBaseFee is a free data retrieval call binding the contract method 0x6146da50. +// +// Solidity: function latestBlobBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksSession) LatestBlobBaseFee() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlobBaseFee(&_L1Blocks.CallOpts) +} + +// LatestBlobBaseFee is a free data retrieval call binding the contract method 0x6146da50. +// +// Solidity: function latestBlobBaseFee() view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) LatestBlobBaseFee() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlobBaseFee(&_L1Blocks.CallOpts) +} + +// LatestBlockHash is a free data retrieval call binding the contract method 0x6c4f6ba9. +// +// Solidity: function latestBlockHash() view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) LatestBlockHash(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestBlockHash") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// LatestBlockHash is a free data retrieval call binding the contract method 0x6c4f6ba9. +// +// Solidity: function latestBlockHash() view returns(bytes32) +func (_L1Blocks *L1BlocksSession) LatestBlockHash() ([32]byte, error) { + return _L1Blocks.Contract.LatestBlockHash(&_L1Blocks.CallOpts) +} + +// LatestBlockHash is a free data retrieval call binding the contract method 0x6c4f6ba9. +// +// Solidity: function latestBlockHash() view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) LatestBlockHash() ([32]byte, error) { + return _L1Blocks.Contract.LatestBlockHash(&_L1Blocks.CallOpts) +} + +// LatestBlockNumber is a free data retrieval call binding the contract method 0x4599c788. +// +// Solidity: function latestBlockNumber() view returns(uint256) +func (_L1Blocks *L1BlocksCaller) LatestBlockNumber(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestBlockNumber") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// LatestBlockNumber is a free data retrieval call binding the contract method 0x4599c788. +// +// Solidity: function latestBlockNumber() view returns(uint256) +func (_L1Blocks *L1BlocksSession) LatestBlockNumber() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlockNumber(&_L1Blocks.CallOpts) +} + +// LatestBlockNumber is a free data retrieval call binding the contract method 0x4599c788. +// +// Solidity: function latestBlockNumber() view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) LatestBlockNumber() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlockNumber(&_L1Blocks.CallOpts) +} + +// LatestBlockTimestamp is a free data retrieval call binding the contract method 0x0c1952d3. +// +// Solidity: function latestBlockTimestamp() view returns(uint256) +func (_L1Blocks *L1BlocksCaller) LatestBlockTimestamp(opts *bind.CallOpts) (*big.Int, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestBlockTimestamp") + + if err != nil { + return *new(*big.Int), err + } + + out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) + + return out0, err + +} + +// LatestBlockTimestamp is a free data retrieval call binding the contract method 0x0c1952d3. +// +// Solidity: function latestBlockTimestamp() view returns(uint256) +func (_L1Blocks *L1BlocksSession) LatestBlockTimestamp() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlockTimestamp(&_L1Blocks.CallOpts) +} + +// LatestBlockTimestamp is a free data retrieval call binding the contract method 0x0c1952d3. +// +// Solidity: function latestBlockTimestamp() view returns(uint256) +func (_L1Blocks *L1BlocksCallerSession) LatestBlockTimestamp() (*big.Int, error) { + return _L1Blocks.Contract.LatestBlockTimestamp(&_L1Blocks.CallOpts) +} + +// LatestParentBeaconRoot is a free data retrieval call binding the contract method 0xa3483d56. +// +// Solidity: function latestParentBeaconRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) LatestParentBeaconRoot(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestParentBeaconRoot") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// LatestParentBeaconRoot is a free data retrieval call binding the contract method 0xa3483d56. +// +// Solidity: function latestParentBeaconRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksSession) LatestParentBeaconRoot() ([32]byte, error) { + return _L1Blocks.Contract.LatestParentBeaconRoot(&_L1Blocks.CallOpts) +} + +// LatestParentBeaconRoot is a free data retrieval call binding the contract method 0xa3483d56. +// +// Solidity: function latestParentBeaconRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) LatestParentBeaconRoot() ([32]byte, error) { + return _L1Blocks.Contract.LatestParentBeaconRoot(&_L1Blocks.CallOpts) +} + +// LatestStateRoot is a free data retrieval call binding the contract method 0x991beafd. +// +// Solidity: function latestStateRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksCaller) LatestStateRoot(opts *bind.CallOpts) ([32]byte, error) { + var out []interface{} + err := _L1Blocks.contract.Call(opts, &out, "latestStateRoot") + + if err != nil { + return *new([32]byte), err + } + + out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) + + return out0, err + +} + +// LatestStateRoot is a free data retrieval call binding the contract method 0x991beafd. +// +// Solidity: function latestStateRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksSession) LatestStateRoot() ([32]byte, error) { + return _L1Blocks.Contract.LatestStateRoot(&_L1Blocks.CallOpts) +} + +// LatestStateRoot is a free data retrieval call binding the contract method 0x991beafd. +// +// Solidity: function latestStateRoot() view returns(bytes32) +func (_L1Blocks *L1BlocksCallerSession) LatestStateRoot() ([32]byte, error) { + return _L1Blocks.Contract.LatestStateRoot(&_L1Blocks.CallOpts) +} + +// SetL1BlockHeader is a paid mutator transaction binding the contract method 0x6a9100cc. +// +// Solidity: function setL1BlockHeader(bytes blockHeaderRlp) returns(bytes32 blockHash) +func (_L1Blocks *L1BlocksTransactor) SetL1BlockHeader(opts *bind.TransactOpts, blockHeaderRlp []byte) (*types.Transaction, error) { + return _L1Blocks.contract.Transact(opts, "setL1BlockHeader", blockHeaderRlp) +} + +// SetL1BlockHeader is a paid mutator transaction binding the contract method 0x6a9100cc. +// +// Solidity: function setL1BlockHeader(bytes blockHeaderRlp) returns(bytes32 blockHash) +func (_L1Blocks *L1BlocksSession) SetL1BlockHeader(blockHeaderRlp []byte) (*types.Transaction, error) { + return _L1Blocks.Contract.SetL1BlockHeader(&_L1Blocks.TransactOpts, blockHeaderRlp) +} + +// SetL1BlockHeader is a paid mutator transaction binding the contract method 0x6a9100cc. +// +// Solidity: function setL1BlockHeader(bytes blockHeaderRlp) returns(bytes32 blockHash) +func (_L1Blocks *L1BlocksTransactorSession) SetL1BlockHeader(blockHeaderRlp []byte) (*types.Transaction, error) { + return _L1Blocks.Contract.SetL1BlockHeader(&_L1Blocks.TransactOpts, blockHeaderRlp) +} + +// L1BlocksImportBlockIterator is returned from FilterImportBlock and is used to iterate over the raw logs and unpacked data for ImportBlock events raised by the L1Blocks contract. +type L1BlocksImportBlockIterator struct { + Event *L1BlocksImportBlock // 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 *L1BlocksImportBlockIterator) 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(L1BlocksImportBlock) + 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(L1BlocksImportBlock) + 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 *L1BlocksImportBlockIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L1BlocksImportBlockIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L1BlocksImportBlock represents a ImportBlock event raised by the L1Blocks contract. +type L1BlocksImportBlock struct { + BlockHash [32]byte + BlockHeight *big.Int + BlockTimestamp *big.Int + BaseFee *big.Int + StateRoot [32]byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterImportBlock is a free log retrieval operation binding the contract event 0xa7823f45e1ee21f9530b77959b57507ad515a14fa9fa24d262ee80e79b2b5745. +// +// Solidity: event ImportBlock(bytes32 indexed blockHash, uint256 blockHeight, uint256 blockTimestamp, uint256 baseFee, bytes32 stateRoot) +func (_L1Blocks *L1BlocksFilterer) FilterImportBlock(opts *bind.FilterOpts, blockHash [][32]byte) (*L1BlocksImportBlockIterator, error) { + + var blockHashRule []interface{} + for _, blockHashItem := range blockHash { + blockHashRule = append(blockHashRule, blockHashItem) + } + + logs, sub, err := _L1Blocks.contract.FilterLogs(opts, "ImportBlock", blockHashRule) + if err != nil { + return nil, err + } + return &L1BlocksImportBlockIterator{contract: _L1Blocks.contract, event: "ImportBlock", logs: logs, sub: sub}, nil +} + +// WatchImportBlock is a free log subscription operation binding the contract event 0xa7823f45e1ee21f9530b77959b57507ad515a14fa9fa24d262ee80e79b2b5745. +// +// Solidity: event ImportBlock(bytes32 indexed blockHash, uint256 blockHeight, uint256 blockTimestamp, uint256 baseFee, bytes32 stateRoot) +func (_L1Blocks *L1BlocksFilterer) WatchImportBlock(opts *bind.WatchOpts, sink chan<- *L1BlocksImportBlock, blockHash [][32]byte) (event.Subscription, error) { + + var blockHashRule []interface{} + for _, blockHashItem := range blockHash { + blockHashRule = append(blockHashRule, blockHashItem) + } + + logs, sub, err := _L1Blocks.contract.WatchLogs(opts, "ImportBlock", blockHashRule) + 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(L1BlocksImportBlock) + if err := _L1Blocks.contract.UnpackLog(event, "ImportBlock", 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 +} + +// ParseImportBlock is a log parse operation binding the contract event 0xa7823f45e1ee21f9530b77959b57507ad515a14fa9fa24d262ee80e79b2b5745. +// +// Solidity: event ImportBlock(bytes32 indexed blockHash, uint256 blockHeight, uint256 blockTimestamp, uint256 baseFee, bytes32 stateRoot) +func (_L1Blocks *L1BlocksFilterer) ParseImportBlock(log types.Log) (*L1BlocksImportBlock, error) { + event := new(L1BlocksImportBlock) + if err := _L1Blocks.contract.UnpackLog(event, "ImportBlock", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 3e0e00ce041e..e8b85a2ab27c 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -29,4 +29,11 @@ var ( L1BaseFeeSlot = common.BigToHash(big.NewInt(1)) OverheadSlot = common.BigToHash(big.NewInt(2)) ScalarSlot = common.BigToHash(big.NewInt(3)) + + // L1BlocksAddress is the address of the L1Blocks predeploy + // see scroll-tech/scroll/contracts/src/L2/predeploys/L1Blocks.sol + L1BlocksAddress = common.HexToAddress("0x5300000000000000000000000000000000000001") + LatestBlockNumberSlot = common.BigToHash(big.NewInt(0)) + + SystemSenderAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") ) diff --git a/rollup/sync_service/bridge_client.go b/rollup/sync_service/bridge_client.go index 51ae3b02ce3e..906958710f17 100644 --- a/rollup/sync_service/bridge_client.go +++ b/rollup/sync_service/bridge_client.go @@ -6,10 +6,14 @@ import ( "fmt" "math/big" + "github.com/scroll-tech/go-ethereum/accounts/abi" "github.com/scroll-tech/go-ethereum/accounts/abi/bind" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/abis" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -20,6 +24,7 @@ type BridgeClient struct { confirmations rpc.BlockNumber l1MessageQueueAddress common.Address filterer *L1MessageQueueFilterer + l1BlocksABI *abi.ABI } func newBridgeClient(ctx context.Context, l1Client EthClient, l1ChainId uint64, confirmations rpc.BlockNumber, l1MessageQueueAddress common.Address) (*BridgeClient, error) { @@ -41,11 +46,18 @@ func newBridgeClient(ctx context.Context, l1Client EthClient, l1ChainId uint64, return nil, fmt.Errorf("failed to initialize L1MessageQueueFilterer, err = %w", err) } + // get the L1Blocks ABI + l1BlocksAbi, err := abis.L1BlocksMetaData.GetAbi() + if err != nil { + return nil, fmt.Errorf("failed to load L1Blocks ABI, err: %w", err) + } + client := BridgeClient{ client: l1Client, confirmations: confirmations, l1MessageQueueAddress: l1MessageQueueAddress, filterer: filterer, + l1BlocksABI: l1BlocksAbi, } return &client, nil @@ -93,6 +105,51 @@ func (c *BridgeClient) fetchMessagesInRange(ctx context.Context, from, to uint64 return msgs, nil } +func (c *BridgeClient) fetchL1Blocks(ctx context.Context, from, to uint64) ([]*types.SystemTx, error) { + if to < from { + return nil, fmt.Errorf("invalid block range from %v to %v", from, to) + } + msgs := make([]*types.SystemTx, (to - from + 1)) + var i uint64 + for i = 0; i < to-from+1; i++ { + msg, err := c.buildL1BlocksTx(ctx, from+i) + if err != nil { + return nil, err + } + msgs[i] = msg + } + return msgs, nil +} + +func (c *BridgeClient) buildL1BlocksTx(ctx context.Context, l1BlockNumber uint64) (*types.SystemTx, error) { + headerRlp, err := c.getL1BlockHeaderRlp(ctx, l1BlockNumber) + if err != nil { + return nil, err + } + data, err := c.l1BlocksABI.Pack("setL1BlockHeader", headerRlp) + if err != nil { + return nil, fmt.Errorf("failed to pack the calldata for setL1BlockHeader, err: %w", err) + } + + return &types.SystemTx{ + Sender: rcfg.SystemSenderAddress, + To: rcfg.L1BlocksAddress, + Data: data, + }, nil +} + +func (c *BridgeClient) getL1BlockHeaderRlp(ctx context.Context, l1BlockNumber uint64) ([]byte, error) { + header, err := c.client.HeaderByNumber(ctx, big.NewInt(int64(l1BlockNumber))) + if err != nil { + return nil, fmt.Errorf("failed to get L1 block header, err: %w", err) + } + headerRlp, err := rlp.EncodeToBytes(header) + if err != nil { + return nil, fmt.Errorf("failed in RLP encoding of L1 block header, err: %w", err) + } + return headerRlp, nil +} + func (c *BridgeClient) getLatestConfirmedBlockNumber(ctx context.Context) (uint64, error) { // confirmation based on "safe" or "finalized" block tag if c.confirmations == rpc.SafeBlockNumber || c.confirmations == rpc.FinalizedBlockNumber { diff --git a/rollup/sync_service/sync_service.go b/rollup/sync_service/sync_service.go index 091f2d19691f..84d44142e036 100644 --- a/rollup/sync_service/sync_service.go +++ b/rollup/sync_service/sync_service.go @@ -8,12 +8,14 @@ import ( "github.com/scroll-tech/go-ethereum/core" "github.com/scroll-tech/go-ethereum/core/rawdb" + "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/metrics" "github.com/scroll-tech/go-ethereum/node" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" ) const ( @@ -34,12 +36,28 @@ const ( // a long section of L1 blocks with no messages and we stop or crash, we will not need to re-scan // this secion. DbWriteThresholdBlocks = 1000 + + // MaxNumCachedL1BlocksTx is the capacity of the L1BlocksTx pool + MaxNumCachedL1BlocksTx = 100 ) var ( l1MessageTotalCounter = metrics.NewRegisteredCounter("rollup/l1/message", nil) ) +type L1BlocksTx struct { + tx *types.SystemTx + blockNumber uint64 +} + +type L1BlocksPool struct { + enabled bool + latestL1BlockNumOnL2 uint64 + l1BlocksTxs []L1BlocksTx // The L1Blocks txs to be included in the blocks + pendingL1BlocksTxs []L1BlocksTx // The L1Blocks txs that are pending confirmation in the blocks + l1BlocksFeed event.Feed +} + // SyncService collects all L1 messages and stores them in a local database. type SyncService struct { ctx context.Context @@ -49,10 +67,11 @@ type SyncService struct { msgCountFeed event.Feed pollInterval time.Duration latestProcessedBlock uint64 + l1BlocksPool L1BlocksPool scope event.SubscriptionScope } -func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, nodeConfig *node.Config, db ethdb.Database, l1Client EthClient) (*SyncService, error) { +func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, nodeConfig *node.Config, db ethdb.Database, bc *core.BlockChain, l1Client EthClient) (*SyncService, error) { // terminate if the caller does not provide an L1 client (e.g. in tests) if l1Client == nil || (reflect.ValueOf(l1Client).Kind() == reflect.Ptr && reflect.ValueOf(l1Client).IsNil()) { log.Warn("No L1 client provided, L1 sync service will not run") @@ -76,6 +95,13 @@ func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, node latestProcessedBlock = *block } + // read the latestL1BlockNumOnL2 from the L1Blocks contract + state, err := bc.StateAt(bc.CurrentBlock().Root()) + if err != nil { + return nil, fmt.Errorf("cannot get the state db: %w", err) + } + latestL1BlockNumOnL2 := state.GetState(rcfg.L1BlocksAddress, rcfg.LatestBlockNumberSlot).Big().Uint64() + ctx, cancel := context.WithCancel(ctx) service := SyncService{ @@ -85,6 +111,10 @@ func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, node db: db, pollInterval: DefaultPollInterval, latestProcessedBlock: latestProcessedBlock, + l1BlocksPool: L1BlocksPool{ + enabled: genesisConfig.Scroll.SystemTx.Enabled, + latestL1BlockNumOnL2: latestL1BlockNumOnL2, + }, } return &service, nil @@ -145,6 +175,41 @@ func (s *SyncService) SubscribeNewL1MsgsEvent(ch chan<- core.NewL1MsgsEvent) eve return s.scope.Track(s.msgCountFeed.Subscribe(ch)) } +func (s *SyncService) SubscribeNewL1BlocksTx(ch chan<- core.NewL1MsgsEvent) event.Subscription { + return s.scope.Track(s.l1BlocksPool.l1BlocksFeed.Subscribe(ch)) +} + +func (s *SyncService) CollectL1BlocksTxs(latestL1BlockNumberOnL2, maxNumTxs uint64) []*types.SystemTx { + if len(s.l1BlocksPool.pendingL1BlocksTxs) > 0 { + // pop the txs from the pending txs in the pool + i := 0 + for ; i < len(s.l1BlocksPool.pendingL1BlocksTxs); i++ { + if s.l1BlocksPool.pendingL1BlocksTxs[i].blockNumber > latestL1BlockNumberOnL2 { + break + } + } + failedTxs := s.l1BlocksPool.pendingL1BlocksTxs[i:] + if len(failedTxs) > 0 { + log.Warn("Failed to process L1Blocks txs", "cnt", len(failedTxs)) + s.l1BlocksPool.l1BlocksTxs = append(failedTxs, s.l1BlocksPool.l1BlocksTxs...) + } + s.l1BlocksPool.pendingL1BlocksTxs = nil + } + + cnt := int(maxNumTxs) + if cnt > len(s.l1BlocksPool.l1BlocksTxs) { + cnt = len(s.l1BlocksPool.l1BlocksTxs) + } + ret := make([]*types.SystemTx, cnt) + for i := 0; i < cnt; i++ { + ret[i] = s.l1BlocksPool.l1BlocksTxs[i].tx + } + s.l1BlocksPool.pendingL1BlocksTxs = s.l1BlocksPool.l1BlocksTxs[:cnt] + s.l1BlocksPool.l1BlocksTxs = s.l1BlocksPool.l1BlocksTxs[cnt:] + + return ret +} + func (s *SyncService) fetchMessages() { latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx) if err != nil { @@ -152,6 +217,53 @@ func (s *SyncService) fetchMessages() { return } + if s.l1BlocksPool.enabled { + s.fetchL1Blocks(latestConfirmed) + } + + s.fetchL1Messages(latestConfirmed) +} + +func (s *SyncService) fetchL1Blocks(latestConfirmed uint64) { + var latestProcessedBlock uint64 + if len(s.l1BlocksPool.l1BlocksTxs) != 0 { + latestProcessedBlock = s.l1BlocksPool.l1BlocksTxs[len(s.l1BlocksPool.l1BlocksTxs)-1].blockNumber + } else { + latestProcessedBlock = s.l1BlocksPool.latestL1BlockNumOnL2 + } + if latestProcessedBlock == 0 { + // This is the first L1 blocks system tx on L2, only fetch the latest confirmed L1 block + latestProcessedBlock = latestConfirmed - 1 + } + // query in batches + num := 0 + for from := latestProcessedBlock + 1; from <= latestConfirmed; from += DefaultFetchBlockRange { + cnt := DefaultFetchBlockRange + if cnt+uint64(len(s.l1BlocksPool.l1BlocksTxs)) > MaxNumCachedL1BlocksTx { + cnt = MaxNumCachedL1BlocksTx - uint64(len(s.l1BlocksPool.l1BlocksTxs)) + } + to := from + cnt - 1 + if to > latestConfirmed { + to = latestConfirmed + } + l1BlocksTxs, err := s.client.fetchL1Blocks(s.ctx, from, to) + if err != nil { + log.Warn("Failed to fetch L1Blocks in range", "fromBlock", from, "toBlock", to, "err", err) + return + } + log.Debug("Received new L1 blocks", "fromBlock", from, "toBlock", to, "count", len(l1BlocksTxs)) + for i, tx := range l1BlocksTxs { + s.l1BlocksPool.l1BlocksTxs = append(s.l1BlocksPool.l1BlocksTxs, L1BlocksTx{tx, from + uint64(i)}) + } + num += len(l1BlocksTxs) + } + + if num > 0 { + s.l1BlocksPool.l1BlocksFeed.Send(core.NewL1MsgsEvent{Count: num}) + } +} + +func (s *SyncService) fetchL1Messages(latestConfirmed uint64) { log.Trace("Sync service fetchMessages", "latestProcessedBlock", s.latestProcessedBlock, "latestConfirmed", latestConfirmed) // keep track of next queue index we're expecting to see