diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index d27687303a60..eabfb21ff8a3 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -75,6 +75,7 @@ jobs: - name: Checkout code uses: actions/checkout@v2 - run: goimports -local github.com/scroll-tech/go-ethereum/ -w . + - run: git diff # If there are any diffs from goimports, fail. - name: Verify no changes from goimports run: | diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index febabccc2b08..3587ae6278c0 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -821,6 +821,7 @@ func (m callMsg) Value() *big.Int { return m.CallMsg.Value } func (m callMsg) Data() []byte { return m.CallMsg.Data } func (m callMsg) AccessList() types.AccessList { return m.CallMsg.AccessList } func (m callMsg) IsL1MessageTx() bool { return false } +func (m callMsg) IsSystemTx() bool { return false } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/core/block_validator.go b/core/block_validator.go index 6773649c6d13..4bee744dd812 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -26,11 +26,13 @@ import ( "github.com/scroll-tech/go-ethereum/core/rawdb" "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/log" "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" ) @@ -69,7 +71,7 @@ func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engin } type tracerWrapper interface { - CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, *types.Block, *types.Block, bool) (*types.BlockTrace, error) + CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, vm.L1Client, *types.Block, *types.Block, bool) (*types.BlockTrace, error) } func (v *BlockValidator) SetupTracerAndCircuitCapacityChecker(tracer tracerWrapper) { @@ -111,6 +113,9 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } return consensus.ErrPrunedAncestor } + if err := v.ValidateSystemTxs(block); err != nil { + return err + } if err := v.ValidateL1Messages(block); err != nil { return err } @@ -137,9 +142,66 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return nil } +// ValidateSystemTxs validates all system txs contained in a block. +// We check that: +// - the sender is a predetermined address +// - the recipient is a system contract +func (v *BlockValidator) ValidateSystemTxs(block *types.Block) error { + // first pass: ensure that system txs are first, in a continuous block + haveSystemTx := false + haveNonSystemTx := false + for _, tx := range block.Transactions() { + if tx.IsSystemTx() { + if !v.config.Scroll.SystemTxEnabled() { + return ErrSystemTxNotEnabled + } + + if haveNonSystemTx { + return consensus.ErrInvalidL1MessageOrder + } + + haveSystemTx = true + continue + } + + haveNonSystemTx = true + } + + if !haveSystemTx { + return nil + } + + for _, tx := range block.Transactions() { + if !tx.IsSystemTx() { + break + } + + stx := tx.AsSystemTx() + + found := false + if stx.Sender != rcfg.SystemSenderAddress { + return ErrUnknownSystemSigner + } + + found = false + for _, contract := range v.config.Scroll.SystemTx.Contracts { + if stx.To == contract { + found = true + break + } + } + + if !found { + return ErrUnknownSystemContract + } + } + + return nil +} + // ValidateL1Messages validates L1 messages contained in a block. // We check the following conditions: -// - L1 messages are in a contiguous section at the front of the block. +// - L1 messages are in a contiguous section at the front of the block, after system txs // - The first L1 message's QueueIndex is right after the last L1 message included in the chain. // - L1 messages follow the QueueIndex order. // - The L1 messages included in the block match the node's view of the L1 ledger. @@ -171,6 +233,10 @@ func (v *BlockValidator) ValidateL1Messages(block *types.Block) error { it := rawdb.IterateL1MessagesFrom(v.bc.db, queueIndex) for _, tx := range block.Transactions() { + if tx.IsSystemTx() { + continue + } + if !tx.IsL1MessageTx() { L1SectionOver = true continue // we do not verify L2 transactions here @@ -298,7 +364,7 @@ func (v *BlockValidator) createTraceEnvAndGetBlockTrace(block *types.Block) (*ty return nil, err } - return v.tracer.CreateTraceEnvAndGetBlockTrace(v.config, v.bc, v.engine, v.bc.db, statedb, parent, block, true) + return v.tracer.CreateTraceEnvAndGetBlockTrace(v.config, v.bc, v.engine, v.bc.db, statedb, v.bc.GetVMConfig().L1Client, parent, block, true) } func (v *BlockValidator) validateCircuitRowConsumption(block *types.Block) (*types.RowConsumption, error) { diff --git a/core/error.go b/core/error.go index a29f56ebf3dc..e23e334002f1 100644 --- a/core/error.go +++ b/core/error.go @@ -104,3 +104,15 @@ var ( // ErrSenderNoEOA is returned if the sender of a transaction is a contract. ErrSenderNoEOA = errors.New("sender not an eoa") ) + +var ( + // ErrUnknownSystemSigner is returned if a sender for a system transaction + // is not a known whitelisted system signer + ErrUnknownSystemSigner = errors.New("unknown system signer") + // ErrUnknownSystemContract is returned if the destination for a system + // transaction is not a known whitelisted system contract + ErrUnknownSystemContract = errors.New("unknown system contract") + // ErrSystemTxNotEnabled is returned if a system tx is included in a block + // but system transactions are not enabled in the sequenced + ErrSystemTxNotEnabled = errors.New("unexpected system transaction") +) diff --git a/core/state_transition.go b/core/state_transition.go index eb292ba27e45..3a79ae2d9db9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -90,6 +90,7 @@ type Message interface { Data() []byte AccessList() types.AccessList IsL1MessageTx() bool + IsSystemTx() bool } // ExecutionResult includes all output after executing given evm @@ -227,6 +228,11 @@ func (st *StateTransition) to() common.Address { } func (st *StateTransition) buyGas() error { + if st.msg.IsSystemTx() { + // no gas accounting for system txs + return nil + } + mgval := new(big.Int).SetUint64(st.msg.Gas()) mgval = mgval.Mul(mgval, st.gasPrice) @@ -266,6 +272,13 @@ func (st *StateTransition) buyGas() error { } func (st *StateTransition) preCheck() error { + if st.msg.IsSystemTx() { + // system tx gas is free and not accounted for. + st.gas += st.msg.Gas() + st.initialGas = st.msg.Gas() + return nil + } + if st.msg.IsL1MessageTx() { // No fee fields to check, no nonce to check, and no need to check if EOA (L1 already verified it for us) // Gas is free, but no refunds! @@ -400,8 +413,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { stateTransitionEvmCallExecutionTimer.Update(time.Since(evmCallStart)) } - // no refunds for l1 messages - if st.msg.IsL1MessageTx() { + // no refunds for l1 messages and system txs + if st.msg.IsL1MessageTx() || st.msg.IsSystemTx() { return &ExecutionResult{ L1DataFee: big.NewInt(0), UsedGas: st.gasUsed(), diff --git a/core/tx_list.go b/core/tx_list.go index 3742d7416e21..30bcf3757cbb 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -572,6 +572,11 @@ func (l *txPricedList) Removed(count int) { // Underpriced checks whether a transaction is cheaper than (or as cheap as) the // lowest priced (remote) transaction currently being tracked. func (l *txPricedList) Underpriced(tx *types.Transaction) bool { + // system txs are never priced + if tx.IsSystemTx() { + return false + } + // Note: with two queues, being underpriced is defined as being worse than the worst item // in all non-empty queues if there is any. If both queues are empty then nothing is underpriced. return (l.underpricedFor(&l.urgent, tx) || len(l.urgent.list) == 0) && diff --git a/core/tx_pool.go b/core/tx_pool.go index a8454204abf0..1a0640529329 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -600,8 +600,8 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // No unauthenticated deposits allowed in the transaction pool. - if tx.IsL1MessageTx() { + // No unauthenticated deposits or system txs allowed in the transaction pool. + if tx.IsL1MessageTx() || tx.IsSystemTx() { return ErrTxTypeNotSupported } diff --git a/core/types/receipt.go b/core/types/receipt.go index 4377ca7e74cb..3a9c3e09578a 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -236,7 +236,7 @@ func (r *Receipt) decodeTyped(b []byte) error { return errEmptyTypedReceipt } switch b[0] { - case DynamicFeeTxType, AccessListTxType, BlobTxType, L1MessageTxType: + case DynamicFeeTxType, AccessListTxType, BlobTxType, L1MessageTxType, SystemTxType: var data receiptRLP err := rlp.DecodeBytes(b[1:], &data) if err != nil { @@ -434,6 +434,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { case L1MessageTxType: w.WriteByte(L1MessageTxType) rlp.Encode(w, data) + case SystemTxType: + w.WriteByte(SystemTxType) + rlp.Encode(w, data) default: // For unsupported types, write nothing. Since this is for // DeriveSha, the error will be caught matching the derived hash diff --git a/core/types/system_tx.go b/core/types/system_tx.go new file mode 100644 index 000000000000..d357987ce080 --- /dev/null +++ b/core/types/system_tx.go @@ -0,0 +1,84 @@ +package types + +import ( + "bytes" + "math/big" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/rlp" +) + +type SystemTx struct { + Sender common.Address // pre-determined sender + To common.Address // system contract address + Data []byte // calldata +} + +// not accountend +const SystemTxGas = 1_000_000 + +func (tx *SystemTx) txType() byte { return SystemTxType } + +func (tx *SystemTx) copy() TxData { + return &SystemTx{ + Sender: tx.Sender, + To: tx.To, + Data: common.CopyBytes(tx.Data), + } +} + +func (tx *SystemTx) chainID() *big.Int { return new(big.Int) } +func (tx *SystemTx) accessList() AccessList { return nil } +func (tx *SystemTx) data() []byte { return tx.Data } +func (tx *SystemTx) gas() uint64 { return SystemTxGas } +func (tx *SystemTx) gasPrice() *big.Int { return new(big.Int) } +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) rawSignatureValues() (v, r, s *big.Int) { + return new(big.Int), new(big.Int), new(big.Int) +} + +func (tx *SystemTx) setSignatureValues(chainID, v, r, s *big.Int) {} + +func (tx *SystemTx) encode(b *bytes.Buffer) error { + return rlp.Encode(b, tx) +} + +func (tx *SystemTx) decode(input []byte) error { + return rlp.DecodeBytes(input, tx) +} + +var _ TxData = (*SystemTx)(nil) + +func NewOrderedSystemTxs(stxs []*SystemTx) *OrderedSystemTxs { + txs := make([]*Transaction, 0, len(stxs)) + for _, stx := range stxs { + txs = append(txs, NewTx(stx)) + } + return &OrderedSystemTxs{txs: txs} +} + +type OrderedSystemTxs struct { + txs []*Transaction +} + +func (o *OrderedSystemTxs) Peek() *Transaction { + if len(o.txs) > 0 { + return o.txs[0] + } + return nil +} + +func (o *OrderedSystemTxs) Shift() { + if len(o.txs) > 0 { + o.txs = o.txs[1:] + } +} + +func (o *OrderedSystemTxs) Pop() {} + +var _ OrderedTransactionSet = (*OrderedSystemTxs)(nil) diff --git a/core/types/transaction.go b/core/types/transaction.go index 12b6ecc47f44..ec5f1dd230c0 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -54,6 +54,7 @@ const ( DynamicFeeTxType = 0x02 BlobTxType = 0x03 + SystemTxType = 0x7D L1MessageTxType = 0x7E ) @@ -200,6 +201,8 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { inner = new(BlobTx) case L1MessageTxType: inner = new(L1MessageTx) + case SystemTxType: + inner = new(SystemTx) default: return nil, ErrTxTypeNotSupported } @@ -365,6 +368,19 @@ func (tx *Transaction) WithoutBlobTxSidecar() *Transaction { return cpy } +// IsSystemTx returns true if the transaction is a system tx +func (tx *Transaction) IsSystemTx() bool { + return tx.Type() == SystemTxType +} + +func (tx *Transaction) AsSystemTx() *SystemTx { + if tx.IsSystemTx() { + return tx.inner.(*SystemTx) + } + + return nil +} + // IsL1MessageTx returns true if the transaction is an L1 cross-domain tx. func (tx *Transaction) IsL1MessageTx() bool { return tx.Type() == L1MessageTxType @@ -424,7 +440,7 @@ func (tx *Transaction) GasTipCapIntCmp(other *big.Int) int { // Note: if the effective gasTipCap is negative, this method returns both error // the actual negative value, _and_ ErrGasFeeCapTooLow func (tx *Transaction) EffectiveGasTip(baseFee *big.Int) (*big.Int, error) { - if tx.IsL1MessageTx() { + if tx.IsL1MessageTx() || tx.IsSystemTx() { return new(big.Int), nil } if baseFee == nil { @@ -748,6 +764,7 @@ type Message struct { accessList AccessList isFake bool isL1MessageTx bool + isSystemTx bool } func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, isFake bool) Message { @@ -764,6 +781,7 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b accessList: accessList, isFake: isFake, isL1MessageTx: false, + isSystemTx: false, } } @@ -781,6 +799,7 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { accessList: tx.AccessList(), isFake: false, isL1MessageTx: tx.IsL1MessageTx(), + isSystemTx: tx.IsSystemTx(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -803,6 +822,7 @@ func (m Message) Data() []byte { return m.data } func (m Message) AccessList() AccessList { return m.accessList } func (m Message) IsFake() bool { return m.isFake } func (m Message) IsL1MessageTx() bool { return m.isL1MessageTx } +func (m Message) IsSystemTx() bool { return m.isSystemTx } // copyAddressPtr copies an address. func copyAddressPtr(a *common.Address) *common.Address { diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index af7707650ef2..3ce7c52402cf 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -166,6 +166,13 @@ func (tx *Transaction) MarshalJSON() ([]byte, error) { enc.Commitments = itx.Sidecar.Commitments enc.Proofs = itx.Sidecar.Proofs } + + case *SystemTx: + gas := itx.gas() + enc.Gas = (*hexutil.Uint64)(&gas) + enc.To = tx.To() + enc.Input = (*hexutil.Bytes)(&itx.Data) + enc.Sender = &itx.Sender } return json.Marshal(&enc) } @@ -449,6 +456,22 @@ func (tx *Transaction) UnmarshalJSON(input []byte) error { } itx.Sender = *dec.Sender + case SystemTxType: + var itx SystemTx + inner = &itx + if dec.To == nil { + return errors.New("missing required field 'to' in transaction") + } + itx.To = *dec.To + if dec.Input == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Input + if dec.Sender == nil { + return errors.New("missing required field 'sender' in transaction") + } + itx.Sender = *dec.Sender + default: return ErrTxTypeNotSupported } diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index 3a9fff64190d..2cebd4abba4c 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -256,6 +256,9 @@ func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { if tx.IsL1MessageTx() { return tx.AsL1MessageTx().Sender, nil } + if tx.IsSystemTx() { + return tx.AsSystemTx().Sender, nil + } if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Sender(tx) } @@ -275,9 +278,14 @@ func (s londonSigner) Equal(s2 Signer) bool { } func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + if tx.IsSystemTx() { + return nil, nil, nil, fmt.Errorf("system tx does not have a signature") + } + if tx.IsL1MessageTx() { - return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature") + return nil, nil, nil, fmt.Errorf("l1 message tx does not have a signature") } + txdata, ok := tx.inner.(*DynamicFeeTx) if !ok { return s.eip2930Signer.SignatureValues(tx, sig) @@ -295,6 +303,10 @@ func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big // Hash returns the hash to be signed by the sender. // It does not uniquely identify the transaction. func (s londonSigner) Hash(tx *Transaction) common.Hash { + if tx.IsSystemTx() { + panic("system tx cannot be signed and do not have a signing hash") + } + if tx.IsL1MessageTx() { panic("l1 message tx cannot be signed and do not have a signing hash") } diff --git a/core/vm/contracts.go b/core/vm/contracts.go index fe6f66feb1a2..166ab2f17671 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -17,6 +17,7 @@ package vm import ( + "context" "crypto/sha256" "encoding/binary" "errors" @@ -28,7 +29,9 @@ import ( "github.com/scroll-tech/go-ethereum/crypto/blake2b" "github.com/scroll-tech/go-ethereum/crypto/bls12381" "github.com/scroll-tech/go-ethereum/crypto/bn256" + "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" //lint:ignore SA1019 Needed for precompile "golang.org/x/crypto/ripemd160" @@ -43,8 +46,8 @@ var ( // requires a deterministic gas count based on the input size of the Run method of the // contract. type PrecompiledContract interface { - RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use - Run(input []byte) ([]byte, error) // Run runs the precompiled contract + RequiredGas(input []byte) uint64 // RequiredPrice calculates the contract gas use + Run(state StateDB, input []byte) ([]byte, error) // Run runs the precompiled contract } // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum @@ -125,6 +128,27 @@ var PrecompiledContractsBernoulli = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{9}): &blake2FDisabled{}, } +// PrecompiledContractsDescartes returns the default set of precompiled contracts, +// including the L1SLoad precompile. +func PrecompiledContractsDescartes(cfg Config) map[common.Address]PrecompiledContract { + if cfg.L1Client == nil { + log.Warn("PrecompiledContractsDescartes: no L1 client") + } + return map[common.Address]PrecompiledContract{ + common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hash{}, + common.BytesToAddress([]byte{3}): &ripemd160hashDisabled{}, + common.BytesToAddress([]byte{4}): &dataCopy{}, + common.BytesToAddress([]byte{5}): &bigModExp{eip2565: true}, + common.BytesToAddress([]byte{6}): &bn256AddIstanbul{}, + common.BytesToAddress([]byte{7}): &bn256ScalarMulIstanbul{}, + common.BytesToAddress([]byte{8}): &bn256PairingIstanbul{}, + common.BytesToAddress([]byte{9}): &blake2FDisabled{}, + // TODO final contract address to be decided + common.BytesToAddress([]byte{1, 1}): &l1sload{l1Client: cfg.L1Client}, + } +} + // PrecompiledContractsBLS contains the set of pre-compiled Ethereum // contracts specified in EIP-2537. These are exported for testing purposes. var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ @@ -140,6 +164,7 @@ var PrecompiledContractsBLS = map[common.Address]PrecompiledContract{ } var ( + PrecompiledAddressesDescartes []common.Address PrecompiledAddressesBernoulli []common.Address PrecompiledAddressesArchimedes []common.Address PrecompiledAddressesBerlin []common.Address @@ -167,11 +192,17 @@ func init() { for k := range PrecompiledContractsBernoulli { PrecompiledAddressesBernoulli = append(PrecompiledAddressesBernoulli, k) } + for k := range PrecompiledContractsDescartes(Config{}) { + PrecompiledAddressesDescartes = append(PrecompiledAddressesDescartes, k) + } + } // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { + case rules.IsDescartes: + return PrecompiledAddressesDescartes case rules.IsBernoulli: return PrecompiledAddressesBernoulli case rules.IsArchimedes: @@ -192,13 +223,13 @@ func ActivePrecompiles(rules params.Rules) []common.Address { // - the returned bytes, // - the _remaining_ gas, // - any error that occurred -func RunPrecompiledContract(p PrecompiledContract, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { +func RunPrecompiledContract(p PrecompiledContract, state StateDB, input []byte, suppliedGas uint64) (ret []byte, remainingGas uint64, err error) { gasCost := p.RequiredGas(input) if suppliedGas < gasCost { return nil, 0, ErrOutOfGas } suppliedGas -= gasCost - output, err := p.Run(input) + output, err := p.Run(state, input) return output, suppliedGas, err } @@ -209,7 +240,7 @@ func (c *ecrecover) RequiredGas(input []byte) uint64 { return params.EcrecoverGas } -func (c *ecrecover) Run(input []byte) ([]byte, error) { +func (c *ecrecover) Run(state StateDB, input []byte) ([]byte, error) { const ecRecoverInputLength = 128 input = common.RightPadBytes(input, ecRecoverInputLength) @@ -250,7 +281,7 @@ type sha256hash struct{} func (c *sha256hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas } -func (c *sha256hash) Run(input []byte) ([]byte, error) { +func (c *sha256hash) Run(state StateDB, input []byte) ([]byte, error) { h := sha256.Sum256(input) return h[:], nil } @@ -260,7 +291,7 @@ type sha256hashDisabled struct{} func (c *sha256hashDisabled) RequiredGas(input []byte) uint64 { return (&sha256hash{}).RequiredGas(input) } -func (c *sha256hashDisabled) Run(input []byte) ([]byte, error) { +func (c *sha256hashDisabled) Run(state StateDB, input []byte) ([]byte, error) { return nil, errPrecompileDisabled } @@ -274,7 +305,7 @@ type ripemd160hash struct{} func (c *ripemd160hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas } -func (c *ripemd160hash) Run(input []byte) ([]byte, error) { +func (c *ripemd160hash) Run(state StateDB, input []byte) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) return common.LeftPadBytes(ripemd.Sum(nil), 32), nil @@ -285,7 +316,7 @@ type ripemd160hashDisabled struct{} func (c *ripemd160hashDisabled) RequiredGas(input []byte) uint64 { return (&ripemd160hash{}).RequiredGas(input) } -func (c *ripemd160hashDisabled) Run(input []byte) ([]byte, error) { +func (c *ripemd160hashDisabled) Run(state StateDB, input []byte) ([]byte, error) { return nil, errPrecompileDisabled } @@ -299,7 +330,7 @@ type dataCopy struct{} func (c *dataCopy) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas } -func (c *dataCopy) Run(in []byte) ([]byte, error) { +func (c *dataCopy) Run(state StateDB, in []byte) ([]byte, error) { return in, nil } @@ -426,7 +457,7 @@ func (c *bigModExp) RequiredGas(input []byte) uint64 { return gas.Uint64() } -func (c *bigModExp) Run(input []byte) ([]byte, error) { +func (c *bigModExp) Run(state StateDB, input []byte) ([]byte, error) { var ( baseLenBigInt = new(big.Int).SetBytes(getData(input, 0, 32)) expLenBigInt = new(big.Int).SetBytes(getData(input, 32, 32)) @@ -509,7 +540,7 @@ func (c *bn256AddIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256AddGasIstanbul } -func (c *bn256AddIstanbul) Run(input []byte) ([]byte, error) { +func (c *bn256AddIstanbul) Run(state StateDB, input []byte) ([]byte, error) { return runBn256Add(input) } @@ -522,7 +553,7 @@ func (c *bn256AddByzantium) RequiredGas(input []byte) uint64 { return params.Bn256AddGasByzantium } -func (c *bn256AddByzantium) Run(input []byte) ([]byte, error) { +func (c *bn256AddByzantium) Run(state StateDB, input []byte) ([]byte, error) { return runBn256Add(input) } @@ -547,7 +578,7 @@ func (c *bn256ScalarMulIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256ScalarMulGasIstanbul } -func (c *bn256ScalarMulIstanbul) Run(input []byte) ([]byte, error) { +func (c *bn256ScalarMulIstanbul) Run(state StateDB, input []byte) ([]byte, error) { return runBn256ScalarMul(input) } @@ -560,7 +591,7 @@ func (c *bn256ScalarMulByzantium) RequiredGas(input []byte) uint64 { return params.Bn256ScalarMulGasByzantium } -func (c *bn256ScalarMulByzantium) Run(input []byte) ([]byte, error) { +func (c *bn256ScalarMulByzantium) Run(state StateDB, input []byte) ([]byte, error) { return runBn256ScalarMul(input) } @@ -619,7 +650,7 @@ func (c *bn256PairingIstanbul) RequiredGas(input []byte) uint64 { return params.Bn256PairingBaseGasIstanbul + uint64(len(input)/192)*params.Bn256PairingPerPointGasIstanbul } -func (c *bn256PairingIstanbul) Run(input []byte) ([]byte, error) { +func (c *bn256PairingIstanbul) Run(state StateDB, input []byte) ([]byte, error) { return runBn256Pairing(input) } @@ -632,7 +663,7 @@ func (c *bn256PairingByzantium) RequiredGas(input []byte) uint64 { return params.Bn256PairingBaseGasByzantium + uint64(len(input)/192)*params.Bn256PairingPerPointGasByzantium } -func (c *bn256PairingByzantium) Run(input []byte) ([]byte, error) { +func (c *bn256PairingByzantium) Run(state StateDB, input []byte) ([]byte, error) { return runBn256Pairing(input) } @@ -658,7 +689,7 @@ var ( errBlake2FInvalidFinalFlag = errors.New("invalid final flag") ) -func (c *blake2F) Run(input []byte) ([]byte, error) { +func (c *blake2F) Run(state StateDB, input []byte) ([]byte, error) { // Make sure the input is valid (correct length and final flag) if len(input) != blake2FInputLength { return nil, errBlake2FInvalidInputLength @@ -702,7 +733,7 @@ type blake2FDisabled struct{} func (c *blake2FDisabled) RequiredGas(input []byte) uint64 { return (&blake2F{}).RequiredGas(input) } -func (c *blake2FDisabled) Run(input []byte) ([]byte, error) { +func (c *blake2FDisabled) Run(state StateDB, input []byte) ([]byte, error) { return nil, errPrecompileDisabled } @@ -721,7 +752,7 @@ func (c *bls12381G1Add) RequiredGas(input []byte) uint64 { return params.Bls12381G1AddGas } -func (c *bls12381G1Add) Run(input []byte) ([]byte, error) { +func (c *bls12381G1Add) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G1Add precompile. // > G1 addition call expects `256` bytes as an input that is interpreted as byte concatenation of two G1 points (`128` bytes each). // > Output is an encoding of addition operation result - single G1 point (`128` bytes). @@ -759,7 +790,7 @@ func (c *bls12381G1Mul) RequiredGas(input []byte) uint64 { return params.Bls12381G1MulGas } -func (c *bls12381G1Mul) Run(input []byte) ([]byte, error) { +func (c *bls12381G1Mul) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G1Mul precompile. // > G1 multiplication call expects `160` bytes as an input that is interpreted as byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). // > Output is an encoding of multiplication operation result - single G1 point (`128` bytes). @@ -809,7 +840,7 @@ func (c *bls12381G1MultiExp) RequiredGas(input []byte) uint64 { return (uint64(k) * params.Bls12381G1MulGas * discount) / 1000 } -func (c *bls12381G1MultiExp) Run(input []byte) ([]byte, error) { +func (c *bls12381G1MultiExp) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G1MultiExp precompile. // G1 multiplication call expects `160*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G1 point (`128` bytes) and encoding of a scalar value (`32` bytes). // Output is an encoding of multiexponentiation operation result - single G1 point (`128` bytes). @@ -852,7 +883,7 @@ func (c *bls12381G2Add) RequiredGas(input []byte) uint64 { return params.Bls12381G2AddGas } -func (c *bls12381G2Add) Run(input []byte) ([]byte, error) { +func (c *bls12381G2Add) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G2Add precompile. // > G2 addition call expects `512` bytes as an input that is interpreted as byte concatenation of two G2 points (`256` bytes each). // > Output is an encoding of addition operation result - single G2 point (`256` bytes). @@ -890,7 +921,7 @@ func (c *bls12381G2Mul) RequiredGas(input []byte) uint64 { return params.Bls12381G2MulGas } -func (c *bls12381G2Mul) Run(input []byte) ([]byte, error) { +func (c *bls12381G2Mul) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G2MUL precompile logic. // > G2 multiplication call expects `288` bytes as an input that is interpreted as byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). // > Output is an encoding of multiplication operation result - single G2 point (`256` bytes). @@ -940,7 +971,7 @@ func (c *bls12381G2MultiExp) RequiredGas(input []byte) uint64 { return (uint64(k) * params.Bls12381G2MulGas * discount) / 1000 } -func (c *bls12381G2MultiExp) Run(input []byte) ([]byte, error) { +func (c *bls12381G2MultiExp) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 G2MultiExp precompile logic // > G2 multiplication call expects `288*k` bytes as an input that is interpreted as byte concatenation of `k` slices each of them being a byte concatenation of encoding of G2 point (`256` bytes) and encoding of a scalar value (`32` bytes). // > Output is an encoding of multiexponentiation operation result - single G2 point (`256` bytes). @@ -983,7 +1014,7 @@ func (c *bls12381Pairing) RequiredGas(input []byte) uint64 { return params.Bls12381PairingBaseGas + uint64(len(input)/384)*params.Bls12381PairingPerPairGas } -func (c *bls12381Pairing) Run(input []byte) ([]byte, error) { +func (c *bls12381Pairing) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 Pairing precompile logic. // > Pairing call expects `384*k` bytes as an inputs that is interpreted as byte concatenation of `k` slices. Each slice has the following structure: // > - `128` bytes of G1 point encoding @@ -1062,7 +1093,7 @@ func (c *bls12381MapG1) RequiredGas(input []byte) uint64 { return params.Bls12381MapG1Gas } -func (c *bls12381MapG1) Run(input []byte) ([]byte, error) { +func (c *bls12381MapG1) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 Map_To_G1 precompile. // > Field-to-curve call expects `64` bytes an an input that is interpreted as a an element of the base field. // > Output of this call is `128` bytes and is G1 point following respective encoding rules. @@ -1097,7 +1128,7 @@ func (c *bls12381MapG2) RequiredGas(input []byte) uint64 { return params.Bls12381MapG2Gas } -func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { +func (c *bls12381MapG2) Run(state StateDB, input []byte) ([]byte, error) { // Implements EIP-2537 Map_FP2_TO_G2 precompile logic. // > Field-to-curve call expects `128` bytes an an input that is interpreted as a an element of the quadratic extension field. // > Output of this call is `256` bytes and is G2 point following respective encoding rules. @@ -1130,3 +1161,47 @@ func (c *bls12381MapG2) Run(input []byte) ([]byte, error) { // Encode the G2 point to 256 bytes return g.EncodePoint(r), nil } + +// L1SLoad precompiled +type l1sload struct { + l1Client L1Client +} + +// RequiredGas returns the gas required to execute the pre-compiled contract. +// FIXED_GAS_COST + (number of storage slots) * PER_LOAD_GAS_COST +// input has format of 20-byte contract address + some number of 32-byte slots, so (len(input) / 32) is number of storage slots +func (c *l1sload) RequiredGas(input []byte) uint64 { + numStorageSlots := len(input) / 32 + if params.L1SloadMaxNumStorageSlots < numStorageSlots { + numStorageSlots = params.L1SloadMaxNumStorageSlots + } + return params.L1SloadBaseGas + uint64(numStorageSlots)*params.L1SloadPerLoadGas +} + +func (c *l1sload) Run(state StateDB, input []byte) ([]byte, error) { + if c.l1Client == nil { + log.Error("No L1Client in the l1sload") + return nil, ErrNoL1Client + } + // verify that the input has 20-byte contract address and 1 to MAX_NUM_STORAGE_SLOTS 32-byte storage slots + numStorageSlots := len(input) / 32 + if numStorageSlots < 1 || numStorageSlots > params.L1SloadMaxNumStorageSlots || len(input) != 20+32*numStorageSlots { + return nil, ErrInvalidInput + } + + // load latest l1 block number known on l2 + block := state.GetState(rcfg.L1BlocksAddress, rcfg.LatestBlockNumberSlot).Big() + + address := common.BytesToAddress(input[0:20]) + keys := make([]common.Hash, numStorageSlots) + for i := range keys { + keys[i] = common.BytesToHash(input[20+32*i : 52+32*i]) + } + + res, err := c.l1Client.StoragesAt(context.Background(), address, keys, block) + if err != nil { + return nil, &ErrL1RPCError{err: err} + } + + return res, nil +} diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index fd0c745b31a4..e77b3ddfd7bf 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -96,7 +96,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) { in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - if res, _, err := RunPrecompiledContract(p, in, gas); err != nil { + if res, _, err := RunPrecompiledContract(p, nil, in, gas); err != nil { t.Error(err) } else if common.Bytes2Hex(res) != test.Expected { t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) @@ -118,7 +118,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) { gas := p.RequiredGas(in) - 1 t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, nil, in, gas) if err.Error() != "out of gas" { t.Errorf("Expected error [out of gas], got [%v]", err) } @@ -135,7 +135,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing in := common.Hex2Bytes(test.Input) gas := p.RequiredGas(in) t.Run(test.Name, func(t *testing.T) { - _, _, err := RunPrecompiledContract(p, in, gas) + _, _, err := RunPrecompiledContract(p, nil, in, gas) if err == nil { t.Errorf("Expected error [%v], got nil", test.ExpectedError) } @@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { bench.ResetTimer() for i := 0; i < bench.N; i++ { copy(data, in) - res, _, err = RunPrecompiledContract(p, data, reqGas) + res, _, err = RunPrecompiledContract(p, nil, data, reqGas) } bench.StopTimer() elapsed := uint64(time.Since(start)) diff --git a/core/vm/errors.go b/core/vm/errors.go index 500acb71a8bb..88d73c453783 100644 --- a/core/vm/errors.go +++ b/core/vm/errors.go @@ -37,6 +37,9 @@ var ( ErrGasUintOverflow = errors.New("gas uint64 overflow") ErrInvalidCode = errors.New("invalid code: must not begin with 0xef") ErrNonceUintOverflow = errors.New("nonce uint64 overflow") + ErrInvalidL1BlockNumber = errors.New("invalid L1 block number") + ErrInvalidInput = errors.New("invalid input") + ErrNoL1Client = errors.New("no available L1 client") ) // ErrStackUnderflow wraps an evm error when the items on the stack less @@ -67,3 +70,10 @@ type ErrInvalidOpCode struct { } func (e *ErrInvalidOpCode) Error() string { return fmt.Sprintf("invalid opcode: %s", e.opcode) } + +// ErrL1RPCError warps an RPC error through the L1 client +type ErrL1RPCError struct { + err error +} + +func (e *ErrL1RPCError) Error() string { return fmt.Sprintf("L1 RPC error: %s", e.err.Error()) } diff --git a/core/vm/evm.go b/core/vm/evm.go index d6f6a6c8271e..5a0e373a709b 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -46,6 +46,8 @@ type ( func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { var precompiles map[common.Address]PrecompiledContract switch { + case evm.chainRules.IsDescartes: + precompiles = PrecompiledContractsDescartes(evm.Config) case evm.chainRules.IsBernoulli: precompiles = PrecompiledContractsBernoulli case evm.chainRules.IsArchimedes: @@ -221,7 +223,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } if isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, evm.StateDB, input, gas) } else { // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -287,7 +289,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, evm.StateDB, input, gas) } else { addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. @@ -331,7 +333,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, evm.StateDB, input, gas) } else { addrCopy := addr // Initialise a new contract and make initialise the delegate values @@ -383,7 +385,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte } if p, isPrecompile := evm.precompile(addr); isPrecompile { - ret, gas, err = RunPrecompiledContract(p, input, gas) + ret, gas, err = RunPrecompiledContract(p, evm.StateDB, input, gas) } else { // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 5dd26061aa9e..aafe5cf25119 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,7 +17,9 @@ package vm import ( + "context" "hash" + "math/big" "sync/atomic" "github.com/scroll-tech/go-ethereum/common" @@ -25,6 +27,11 @@ import ( "github.com/scroll-tech/go-ethereum/log" ) +// L1Client provides functionality provided by L1 +type L1Client interface { + StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) +} + // Config are the configuration options for the Interpreter type Config struct { Debug bool // Enables debugging @@ -36,6 +43,8 @@ type Config struct { JumpTable [256]*operation // EVM instruction table, automatically populated if unset ExtraEips []int // Additional EIPS that are to be enabled + + L1Client L1Client // L1 RPC client } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/eth/api_backend.go b/eth/api_backend.go index 81b70a857363..fff8c3fa8060 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -390,3 +390,7 @@ func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Blo func (b *EthAPIBackend) StateAt(root common.Hash) (*state.StateDB, error) { return b.eth.BlockChain().StateAt(root) } + +func (b *EthAPIBackend) L1Client() vm.L1Client { + return b.eth.BlockChain().GetVMConfig().L1Client +} diff --git a/eth/backend.go b/eth/backend.go index 91bbc47e4ae8..6a8751fe879a 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -181,6 +181,7 @@ func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthCl var ( vmConfig = vm.Config{ EnablePreimageRecording: config.EnablePreimageRecording, + L1Client: l1Client, } cacheConfig = &core.CacheConfig{ TrieCleanLimit: config.TrieCleanCache, @@ -218,7 +219,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) } diff --git a/eth/handler.go b/eth/handler.go index 91adb2a62b5b..509f08178d52 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -480,7 +480,7 @@ func (h *handler) BroadcastTransactions(txs types.Transactions) { // Broadcast transactions to a batch of peers not knowing about it for _, tx := range txs { // L1 messages are not broadcast to peers - if tx.IsL1MessageTx() { + if tx.IsL1MessageTx() || tx.IsSystemTx() { continue } peers := h.peers.peersWithoutTransaction(tx.Hash()) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index e4c3957c603d..22bbb151e189 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -77,6 +77,7 @@ type Backend interface { CacheConfig() *core.CacheConfig Engine() consensus.Engine ChainDb() ethdb.Database + L1Client() vm.L1Client // StateAtBlock returns the state corresponding to the stateroot of the block. // N.B: For executing transactions on block N, the required stateRoot is block N-1, // so this method should be called with the parent. @@ -766,6 +767,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block EnablePreimageRecording: true, } } + vmConf.L1Client = api.backend.L1Client() // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) statedb.SetTxContext(tx.Hash(), i) @@ -940,7 +942,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex tracer = vm.NewStructLogger(config.LogConfig) } // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) + vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{L1Client: api.backend.L1Client(), Debug: true, Tracer: tracer, NoBaseFee: true}) // If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`. if message.GasPrice().Cmp(big.NewInt(0)) == 0 { diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index a52daa29983d..8b5b00a2451a 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -23,7 +23,7 @@ type TraceBlock interface { } type scrollTracerWrapper interface { - CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, core.ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, *types.Block, *types.Block, bool) (*types.BlockTrace, error) + CreateTraceEnvAndGetBlockTrace(*params.ChainConfig, core.ChainContext, consensus.Engine, ethdb.Database, *state.StateDB, vm.L1Client, *types.Block, *types.Block, bool) (*types.BlockTrace, error) } // GetBlockTraceByNumberOrHash replays the block and returns the structured BlockTrace by hash or number. @@ -109,5 +109,5 @@ func (api *API) createTraceEnvAndGetBlockTrace(ctx context.Context, config *Trac } chaindb := api.backend.ChainDb() - return api.scrollTracerWrapper.CreateTraceEnvAndGetBlockTrace(api.backend.ChainConfig(), api.chainContext(ctx), api.backend.Engine(), chaindb, statedb, parent, block, true) + return api.scrollTracerWrapper.CreateTraceEnvAndGetBlockTrace(api.backend.ChainConfig(), api.chainContext(ctx), api.backend.Engine(), chaindb, statedb, api.backend.L1Client(), parent, block, true) } diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 3a209c752b5a..581a39564546 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -143,6 +143,10 @@ func (b *testBackend) ChainDb() ethdb.Database { return b.chaindb } +func (b *testBackend) L1Client() vm.L1Client { + return nil +} + func (b *testBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, checkLive bool, preferDisk bool) (*state.StateDB, error) { statedb, err := b.chain.StateAt(block.Root()) if err != nil { diff --git a/ethclient/ethclient.go b/ethclient/ethclient.go index 1d110428ef26..433edd26dba6 100644 --- a/ethclient/ethclient.go +++ b/ethclient/ethclient.go @@ -509,6 +509,31 @@ func (ec *Client) StorageAt(ctx context.Context, account common.Address, key com return result, err } +// StoragesAt returns the values of keys in the contract storage of the given account. +// The block number can be nil, in which case the value is taken from the latest known block. +func (ec *Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + results := make([]hexutil.Bytes, len(keys)) + reqs := make([]rpc.BatchElem, len(keys)) + for i := range reqs { + reqs[i] = rpc.BatchElem{ + Method: "eth_getStorageAt", + Args: []interface{}{account, keys[i], toBlockNumArg(blockNumber)}, + Result: &results[i], + } + } + if err := ec.c.BatchCallContext(ctx, reqs); err != nil { + return nil, err + } + output := make([]byte, 32*len(keys)) + for i := range reqs { + if reqs[i].Error != nil { + return nil, reqs[i].Error + } + copy(output[i*32:], results[i]) + } + return output, nil +} + // CodeAt returns the contract code of the given account. // The block number can be nil, in which case the code is taken from the latest known block. func (ec *Client) CodeAt(ctx context.Context, account common.Address, blockNumber *big.Int) ([]byte, error) { diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index d89bbcb50a6e..426e3978a222 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -617,6 +617,17 @@ func testAtFunctions(t *testing.T, client *rpc.Client) { if !bytes.Equal(storage, penStorage) { t.Fatalf("unexpected storage: %v %v", storage, penStorage) } + // StoragesAt + storages, err := ec.StoragesAt(context.Background(), testAddr, []common.Hash{{}, {}}, nil) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !bytes.Equal(storages[0:32], penStorage) { + t.Fatalf("unexpected storage: %v %v", storages[0:32], penStorage) + } + if !bytes.Equal(storages[32:64], penStorage) { + t.Fatalf("unexpected storage: %v %v", storages[32:64], penStorage) + } // CodeAt code, err := ec.CodeAt(context.Background(), testAddr, nil) if err != nil { diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index b41bd33fd84d..d37177176314 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -982,7 +982,7 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash if err != nil { return nil, err } - evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{NoBaseFee: true}) + evm, vmError, err := b.GetEVM(ctx, msg, state, header, &vm.Config{L1Client: b.L1Client(), NoBaseFee: true}) if err != nil { return nil, err } @@ -1363,6 +1363,9 @@ func NewRPCTransaction(tx *types.Transaction, blockHash common.Hash, blockNumber msg := tx.AsL1MessageTx() result.Sender = &msg.Sender result.QueueIndex = (*hexutil.Uint64)(&msg.QueueIndex) + case types.SystemTxType: + msg := tx.AsSystemTx() + result.Sender = &msg.Sender } return result } @@ -1490,7 +1493,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH // Apply the transaction with the access list tracer tracer := vm.NewAccessListTracer(accessList, args.from(), to, precompiles) - config := vm.Config{Tracer: tracer, Debug: true, NoBaseFee: true} + config := vm.Config{L1Client: b.L1Client(), Tracer: tracer, Debug: true, NoBaseFee: true} vmenv, _, err := b.GetEVM(ctx, msg, statedb, header, &config) if err != nil { return nil, 0, nil, err diff --git a/internal/ethapi/backend.go b/internal/ethapi/backend.go index b2be3ae41a0a..4fcafc63e391 100644 --- a/internal/ethapi/backend.go +++ b/internal/ethapi/backend.go @@ -94,6 +94,7 @@ type Backend interface { ChainConfig() *params.ChainConfig Engine() consensus.Engine + L1Client() vm.L1Client } func GetAPIs(apiBackend Backend) []rpc.API { diff --git a/les/api_backend.go b/les/api_backend.go index 8c019cf3c3cc..6b51318aa5a3 100644 --- a/les/api_backend.go +++ b/les/api_backend.go @@ -340,3 +340,8 @@ func (b *LesApiBackend) StateAtTransaction(ctx context.Context, block *types.Blo func (b *LesApiBackend) StateAt(root common.Hash) (*state.StateDB, error) { return nil, fmt.Errorf("StateAt is not supported in LES protocol") } + +func (b *LesApiBackend) L1Client() vm.L1Client { + // TODO: support L1 client in LesApiBackend + return nil +} diff --git a/miner/miner.go b/miner/miner.go index 95992b90000f..3bf57b32f10c 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -76,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 56f6c1f2da88..ebd8c643c453 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -67,6 +67,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 330fb2cbbbf6..3450e3ec12c6 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" ) @@ -102,10 +103,12 @@ var ( l2CommitTxApplyTimer = metrics.NewRegisteredTimer("miner/commit/tx_apply", nil) l2CommitNewWorkTimer = metrics.NewRegisteredTimer("miner/commit/new_work_all", nil) + l2CommitNewWorkSystemTxTimer = metrics.NewRegisteredTimer("miner/commit/new_work_system_tx", nil) l2CommitNewWorkL1CollectTimer = metrics.NewRegisteredTimer("miner/commit/new_work_collect_l1", nil) l2CommitNewWorkPrepareTimer = metrics.NewRegisteredTimer("miner/commit/new_work_prepare", nil) l2CommitNewWorkCommitUncleTimer = metrics.NewRegisteredTimer("miner/commit/new_work_uncle", nil) l2CommitNewWorkTidyPendingTxTimer = metrics.NewRegisteredTimer("miner/commit/new_work_tidy_pending", nil) + l2CommitNewWorkCommitSystemTxTimer = metrics.NewRegisteredTimer("miner/commit/new_work_commit_system_tx", nil) l2CommitNewWorkCommitL1MsgTimer = metrics.NewRegisteredTimer("miner/commit/new_work_commit_l1_msg", nil) l2CommitNewWorkPrioritizedTxCommitTimer = metrics.NewRegisteredTimer("miner/commit/new_work_prioritized", nil) l2CommitNewWorkRemoteLocalCommitTimer = metrics.NewRegisteredTimer("miner/commit/new_work_remote_local", nil) @@ -199,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 @@ -229,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). @@ -269,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), @@ -296,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) @@ -461,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) { @@ -490,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 } @@ -632,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 @@ -824,7 +846,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { // don't commit the state during tracing for circuit capacity checker, otherwise we cannot revert. // and even if we don't commit the state, the `refund` value will still be correct, as explained in `CommitTransaction` commitStateAfterApply := false - traceEnv, err := tracing.CreateTraceEnv(w.chainConfig, w.chain, w.engine, w.eth.ChainDb(), state, parent, + traceEnv, err := tracing.CreateTraceEnv(w.chainConfig, w.chain, w.engine, w.eth.ChainDb(), state, w.chain.GetVMConfig().L1Client, parent, // new block with a placeholder tx, for traceEnv's ExecutionResults length & TxStorageTraces length types.NewBlockWithHeader(header).WithBody([]*types.Transaction{types.NewTx(&types.LegacyTx{})}, nil), commitStateAfterApply) @@ -1328,6 +1350,12 @@ func (w *worker) collectPendingL1Messages(startIndex uint64) []types.L1MessageTx return rawdb.ReadL1MessagesFrom(w.eth.ChainDb(), startIndex, maxCount) } +func (w *worker) emitSystemTxs(env *environment) []*types.SystemTx { + latestL1BlockNumberOnL2 := env.state.GetState(rcfg.L1BlocksAddress, rcfg.LatestBlockNumberSlot).Big().Uint64() + log.Info("Latest L1 block number on L2", "l1BlockNum", latestL1BlockNumberOnL2, "l2BlockNum", env.header.Number.Uint64()) + return w.eth.SyncService().CollectL1BlocksTxs(latestL1BlockNumberOnL2, w.chainConfig.Scroll.L1Config.MaxNumL1BlocksTxPerBlock) +} + // commitNewWork generates several new sealing tasks based on the parent block. func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) { w.mu.RLock() @@ -1436,6 +1464,15 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) if !noempty && atomic.LoadUint32(&w.noempty) == 0 { w.commit(uncles, nil, false, tstart) } + + // emit system txs + var systemTxs []*types.SystemTx + if w.chainConfig.Scroll.SystemTxEnabled() { + common.WithTimer(l2CommitNewWorkSystemTxTimer, func() { + systemTxs = w.emitSystemTxs(env) + }) + } + // fetch l1Txs var l1Messages []types.L1MessageTx if w.chainConfig.Scroll.ShouldIncludeL1Messages() { @@ -1450,11 +1487,12 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) // Short circuit if there is no available pending transactions. // But if we disable empty precommit already, ignore it. Since // empty block is necessary to keep the liveness of the network. - if len(pending) == 0 && len(l1Messages) == 0 && atomic.LoadUint32(&w.noempty) == 0 { + if len(pending) == 0 && len(systemTxs) == 0 && len(l1Messages) == 0 && atomic.LoadUint32(&w.noempty) == 0 { w.updateSnapshot() l2CommitNewWorkTidyPendingTxTimer.UpdateSince(tidyPendingStart) return } + // Split the pending transactions into locals and remotes localTxs, remoteTxs := make(map[common.Address]types.Transactions), pending for _, account := range w.eth.TxPool().Locals() { @@ -1466,8 +1504,23 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) l2CommitNewWorkTidyPendingTxTimer.UpdateSince(tidyPendingStart) var skipCommit, circuitCapacityOrBlockTimeReached bool + + commitSystemTxStart := time.Now() + if w.chainConfig.Scroll.SystemTxEnabled() { + log.Trace("Processing SystemTxs for inclusion", "count", len(systemTxs)) + + txs := types.NewOrderedSystemTxs(systemTxs) + skipCommit, circuitCapacityOrBlockTimeReached = 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 + } + } + l2CommitNewWorkCommitSystemTxTimer.UpdateSince(commitSystemTxStart) + commitL1MsgStart := time.Now() - if w.chainConfig.Scroll.ShouldIncludeL1Messages() && len(l1Messages) > 0 { + if w.chainConfig.Scroll.ShouldIncludeL1Messages() && len(l1Messages) > 0 && !circuitCapacityOrBlockTimeReached { log.Trace("Processing L1 messages for inclusion", "count", len(l1Messages)) txs, err := types.NewL1MessagesByQueueIndex(l1Messages) if err != nil { diff --git a/miner/worker_test.go b/miner/worker_test.go index 6e099905f3e1..e20dc00dae47 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -177,6 +177,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 e0e3b4147329..c23ad742e6c8 100644 --- a/params/config.go +++ b/params/config.go @@ -370,10 +370,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"), }, }, } @@ -389,7 +390,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 @@ -403,7 +404,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), big.NewInt(0), nil, new(EthashConfig), nil, @@ -412,7 +413,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)) @@ -422,7 +423,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")}, }} ) @@ -535,14 +536,25 @@ type ScrollConfig struct { // L1 config L1Config *L1Config `json:"l1Config,omitempty"` + + // System contract config + SystemTx *SystemTxConfig `json:"systemTxConfig,omitempty"` +} + +type SystemTxConfig struct { + Enabled bool `json:"enabled"` + + // TODO maybe move these in state (system contract) + 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 { @@ -566,6 +578,10 @@ func (s ScrollConfig) ShouldIncludeL1Messages() bool { return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0 } +func (s ScrollConfig) SystemTxEnabled() bool { + return s.SystemTx != nil && s.SystemTx.Enabled +} + func (s ScrollConfig) String() string { maxTxPerBlock := "" if s.MaxTxPerBlock != nil { diff --git a/params/protocol_params.go b/params/protocol_params.go index 8634533408c9..3bc1adc9cd5d 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -155,6 +155,9 @@ const ( Bls12381MapG1Gas uint64 = 5500 // Gas price for BLS12-381 mapping field element to G1 operation Bls12381MapG2Gas uint64 = 110000 // Gas price for BLS12-381 mapping field element to G2 operation + L1SloadBaseGas uint64 = 2000 // Base price for L1Sload + L1SloadPerLoadGas uint64 = 2000 // Per-load price for loading one storage slot + // The Refund Quotient is the cap on how much of the used gas can be refunded. Before EIP-3529, // up to half the consumed gas could be refunded. Redefined as 1/5th in EIP-3529 RefundQuotient uint64 = 2 @@ -165,6 +168,8 @@ const ( BlobTxBlobGaspriceUpdateFraction = 3338477 // Controls the maximum rate of change for blob gas price BlobTxTargetBlobGasPerBlock = 3 * BlobTxBlobGasPerBlob // Target consumable blob gas for data blobs per block (for 1559-like pricing) + + L1SloadMaxNumStorageSlots = 5 // Max number of storage slots requested in L1Sload precompile ) // Gas discount table for BLS12-381 G1 and G2 multi exponentiation operations 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..72a76054d51a 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -29,4 +29,12 @@ 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)) + L1BlockBufferSize = 8192 + + SystemSenderAddress = common.HexToAddress("0xffffFFFfFFffffffffffffffFfFFFfffFFFfFFfE") ) diff --git a/rollup/rollup_sync_service/l1client_test.go b/rollup/rollup_sync_service/l1client_test.go index 5b9de1274300..fb88f790fe29 100644 --- a/rollup/rollup_sync_service/l1client_test.go +++ b/rollup/rollup_sync_service/l1client_test.go @@ -72,3 +72,11 @@ func (m *mockEthClient) TransactionByHash(ctx context.Context, txHash common.Has func (m *mockEthClient) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) { return nil, nil } + +func (m *mockEthClient) StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} + +func (m *mockEthClient) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + return nil, nil +} 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..7038e1349142 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,14 @@ 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() + log.Info("Latest L1 block number on L2", "l1BlockNum", latestL1BlockNumOnL2) + ctx, cancel := context.WithCancel(ctx) service := SyncService{ @@ -85,6 +112,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 +176,43 @@ 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.latestL1BlockNumOnL2 = latestL1BlockNumberOnL2 + s.l1BlocksPool.pendingL1BlocksTxs = s.l1BlocksPool.l1BlocksTxs[:cnt] + s.l1BlocksPool.l1BlocksTxs = s.l1BlocksPool.l1BlocksTxs[cnt:] + log.Info("Collected L1Blocks txs", "cnt", len(ret), "remaining", len(s.l1BlocksPool.l1BlocksTxs)) + + return ret +} + func (s *SyncService) fetchMessages() { latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx) if err != nil { @@ -152,6 +220,58 @@ 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 + from := latestProcessedBlock + 1 + for from <= latestConfirmed { + cnt := DefaultFetchBlockRange + if cnt+uint64(len(s.l1BlocksPool.l1BlocksTxs)) > MaxNumCachedL1BlocksTx { + cnt = MaxNumCachedL1BlocksTx - uint64(len(s.l1BlocksPool.l1BlocksTxs)) + } + if cnt == 0 { + break + } + 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.Info("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) + from = to + 1 + } + + 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 diff --git a/rollup/sync_service/types.go b/rollup/sync_service/types.go index 3429ec1bb778..76a5a9355574 100644 --- a/rollup/sync_service/types.go +++ b/rollup/sync_service/types.go @@ -19,4 +19,6 @@ type EthClient interface { SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) TransactionByHash(ctx context.Context, txHash common.Hash) (tx *types.Transaction, isPending bool, err error) BlockByHash(ctx context.Context, hash common.Hash) (*types.Block, error) + StorageAt(ctx context.Context, account common.Address, key common.Hash, blockNumber *big.Int) ([]byte, error) + StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) } diff --git a/rollup/tracing/tracing.go b/rollup/tracing/tracing.go index 321b3885ecc9..6589d5f7e05a 100644 --- a/rollup/tracing/tracing.go +++ b/rollup/tracing/tracing.go @@ -46,8 +46,8 @@ func NewTracerWrapper() *TracerWrapper { } // CreateTraceEnvAndGetBlockTrace wraps the whole block tracing logic for a block -func (tw *TracerWrapper) CreateTraceEnvAndGetBlockTrace(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, parent *types.Block, block *types.Block, commitAfterApply bool) (*types.BlockTrace, error) { - traceEnv, err := CreateTraceEnv(chainConfig, chainContext, engine, chaindb, statedb, parent, block, commitAfterApply) +func (tw *TracerWrapper) CreateTraceEnvAndGetBlockTrace(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, l1Client vm.L1Client, parent *types.Block, block *types.Block, commitAfterApply bool) (*types.BlockTrace, error) { + traceEnv, err := CreateTraceEnv(chainConfig, chainContext, engine, chaindb, statedb, l1Client, parent, block, commitAfterApply) if err != nil { return nil, err } @@ -59,6 +59,7 @@ type TraceEnv struct { logConfig *vm.LogConfig commitAfterApply bool chainConfig *params.ChainConfig + l1Client vm.L1Client coinbase common.Address @@ -98,12 +99,13 @@ type txTraceTask struct { index int } -func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConfig, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv { +func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConfig, l1Client vm.L1Client, blockCtx vm.BlockContext, startL1QueueIndex uint64, coinbase common.Address, statedb *state.StateDB, rootBefore common.Hash, block *types.Block, commitAfterApply bool) *TraceEnv { return &TraceEnv{ logConfig: logConfig, commitAfterApply: commitAfterApply, chainConfig: chainConfig, coinbase: coinbase, + l1Client: l1Client, signer: types.MakeSigner(chainConfig, block.Number()), state: statedb, blockCtx: blockCtx, @@ -120,7 +122,7 @@ func CreateTraceEnvHelper(chainConfig *params.ChainConfig, logConfig *vm.LogConf } } -func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) { +func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainContext, engine consensus.Engine, chaindb ethdb.Database, statedb *state.StateDB, l1Client vm.L1Client, parent *types.Block, block *types.Block, commitAfterApply bool) (*TraceEnv, error) { var coinbase common.Address var err error @@ -157,6 +159,7 @@ func CreateTraceEnv(chainConfig *params.ChainConfig, chainContext core.ChainCont EnableMemory: false, EnableReturnData: true, }, + l1Client, core.NewEVMBlockContext(block.Header(), chainContext, chainConfig, nil), *startL1QueueIndex, coinbase, @@ -228,7 +231,7 @@ func (env *TraceEnv) GetBlockTrace(block *types.Block) (*types.BlockTrace, error // Generate the next state snapshot fast without tracing msg, _ := tx.AsMessage(env.signer, block.BaseFee()) env.state.SetTxContext(tx.Hash(), i) - vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{}) + vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, env.chainConfig, vm.Config{L1Client: env.l1Client}) l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) if err != nil { failed = err @@ -329,7 +332,7 @@ func (env *TraceEnv) getTxResult(state *state.StateDB, index int, block *types.B structLogger := vm.NewStructLogger(env.logConfig) tracer := NewMuxTracer(structLogger, callTracer, prestateTracer) // Run the transaction with tracing enabled. - vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) + vmenv := vm.NewEVM(env.blockCtx, txContext, state, env.chainConfig, vm.Config{L1Client: env.l1Client, Debug: true, Tracer: tracer, NoBaseFee: true}) // Call Prepare to clear out the statedb access list state.SetTxContext(txctx.TxHash, txctx.TxIndex) diff --git a/tests/fuzzers/bls12381/precompile_fuzzer.go b/tests/fuzzers/bls12381/precompile_fuzzer.go index 648138a95643..6a69543856af 100644 --- a/tests/fuzzers/bls12381/precompile_fuzzer.go +++ b/tests/fuzzers/bls12381/precompile_fuzzer.go @@ -72,8 +72,10 @@ func checkInput(id byte, inputLen int) bool { // The fuzzer functions must return // 1 if the fuzzer should increase priority of the -// given input during subsequent fuzzing (for example, the input is lexically -// correct and was parsed successfully); +// +// given input during subsequent fuzzing (for example, the input is lexically +// correct and was parsed successfully); +// // -1 if the input must not be added to corpus even if gives new coverage; and // 0 otherwise // other values are reserved for future use. @@ -90,7 +92,7 @@ func fuzz(id byte, data []byte) int { } cpy := make([]byte, len(data)) copy(cpy, data) - _, err := precompile.Run(cpy) + _, err := precompile.Run(nil, cpy) if !bytes.Equal(cpy, data) { panic(fmt.Sprintf("input data modified, precompile %d: %x %x", id, data, cpy)) }