From 910d4f1a70fc29d4433b9c793191e29203489d9d Mon Sep 17 00:00:00 2001 From: Ho Date: Thu, 18 May 2023 21:49:09 +0800 Subject: [PATCH 01/22] feat(trace): add storage proof about l1fee (baseFee, overhead, scalar) and withdraw root into trace (#314) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add proof for predeployed storages * reverse inneeded code * update for mainbranch merging * add coinbase storage as trace * comment for clarify * Update version.go --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- eth/tracers/api_blocktrace.go | 37 +++++++++++++++++++++++++++++++++++ params/version.go | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 851bf9549..030cabe01 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -290,6 +290,8 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc // merge required proof data proofAccounts := tracer.UpdatedAccounts() + proofAccounts[vmenv.FeeRecipient()] = struct{}{} + proofAccounts[rcfg.L1GasPriceOracleAddress] = struct{}{} for addr := range proofAccounts { addrStr := addr.String() @@ -314,6 +316,12 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc } proofStorages := tracer.UpdatedStorages() + proofStorages[rcfg.L1GasPriceOracleAddress] = vm.Storage( + map[common.Hash]common.Hash{ + rcfg.L1BaseFeeSlot: {}, // notice we do not need the right value here + rcfg.OverheadSlot: {}, + rcfg.ScalarSlot: {}, + }) for addr, keys := range proofStorages { env.sMu.Lock() trie, err := state.GetStorageTrieForProof(addr) @@ -404,6 +412,35 @@ func (api *API) fillBlockTrace(env *traceEnv, block *types.Block) (*types.BlockT txs[i] = types.NewTransactionData(tx, block.NumberU64(), api.backend.ChainConfig()) } + if _, existed := env.Proofs[rcfg.L2MessageQueueAddress.String()]; !existed { + if proof, err := statedb.GetProof(rcfg.L2MessageQueueAddress); err != nil { + log.Error("Proof for L2MessageQueueAddress not available", "error", err) + } else { + wrappedProof := make([]hexutil.Bytes, len(proof)) + for i, bt := range proof { + wrappedProof[i] = bt + } + env.Proofs[rcfg.L2MessageQueueAddress.String()] = wrappedProof + } + } + + if _, existed := env.StorageProofs[rcfg.L2MessageQueueAddress.String()]; !existed { + env.StorageProofs[rcfg.L2MessageQueueAddress.String()] = make(map[string][]hexutil.Bytes) + } + if _, existed := env.StorageProofs[rcfg.L2MessageQueueAddress.String()][rcfg.WithdrawTrieRootSlot.String()]; !existed { + if trie, err := statedb.GetStorageTrieForProof(rcfg.L2MessageQueueAddress); err != nil { + log.Error("Storage proof for WithdrawTrieRootSlot not available", "error", err) + } else if proof, _ := statedb.GetSecureTrieProof(trie, rcfg.WithdrawTrieRootSlot); err != nil { + log.Error("Get storage proof for WithdrawTrieRootSlot failed", "error", err) + } else { + wrappedProof := make([]hexutil.Bytes, len(proof)) + for i, bt := range proof { + wrappedProof[i] = bt + } + env.StorageProofs[rcfg.L2MessageQueueAddress.String()][rcfg.WithdrawTrieRootSlot.String()] = wrappedProof + } + } + blockTrace := &types.BlockTrace{ ChainID: api.backend.ChainConfig().ChainID.Uint64(), Version: params.ArchiveVersion(params.CommitHash), diff --git a/params/version.go b/params/version.go index 36583679e..a26f0b46c 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) From 1094569b5b4c243099982caed5be4a7850adf6d2 Mon Sep 17 00:00:00 2001 From: Nazarii Denha Date: Fri, 19 May 2023 15:41:58 +0200 Subject: [PATCH 02/22] feat: enable eip and update check (#335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enable eip and update check * Update version.go --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- core/tx_pool.go | 5 +++++ core/vm/jump_table.go | 5 ++++- params/version.go | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/core/tx_pool.go b/core/tx_pool.go index cfa86f988..5fa2b5c47 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -18,6 +18,7 @@ package core import ( "errors" + "fmt" "math" "math/big" "sort" @@ -614,6 +615,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { if uint64(tx.Size()) > txMaxSize { return ErrOversizedData } + // Check whether the init code size has been exceeded. + if pool.shanghai && tx.To() == nil && len(tx.Data()) > params.MaxInitCodeSize { + return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, len(tx.Data()), params.MaxInitCodeSize) + } // Transactions can't be negative. This may never happen using RLP decoded // transactions but may occur if you create a transaction using the RPC. if tx.Value().Sign() < 0 { diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index e7e98339d..4112361b3 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -64,9 +64,12 @@ var ( // JumpTable contains the EVM opcodes supported at a given fork. type JumpTable [256]*operation +// newShanghaiInstructionSet returns the frontier, homestead, byzantium, +// contantinople, istanbul, petersburg, berlin, london and shanghai instructions. func newShanghaiInstructionSet() JumpTable { instructionSet := newLondonInstructionSet() - enable3860(&instructionSet) + enable3855(&instructionSet) // PUSH0 instruction https://eips.ethereum.org/EIPS/eip-3855 + enable3860(&instructionSet) // Limit and meter initcode https://eips.ethereum.org/EIPS/eip-3860 return instructionSet } diff --git a/params/version.go b/params/version.go index a26f0b46c..b745a58bd 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionPatch = 2 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) From 149419cf09eb4e8016c96af40f81a67b9a289ebf Mon Sep 17 00:00:00 2001 From: Ho Date: Mon, 22 May 2023 15:24:49 +0800 Subject: [PATCH 03/22] fix(trace): deletion proof missed path terminated by empty node (#330) * fix deletion proof issue on empty node * refine for better implement and fix unittest * Update version.go --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- params/version.go | 2 +- trie/zk_trie_proof_test.go | 5 +++-- trie/zktrie_deletionproof.go | 13 ++++++++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index b745a58bd..ce0873da8 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release + VersionPatch = 3 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) diff --git a/trie/zk_trie_proof_test.go b/trie/zk_trie_proof_test.go index 0109e9be8..9246a7a99 100644 --- a/trie/zk_trie_proof_test.go +++ b/trie/zk_trie_proof_test.go @@ -244,11 +244,12 @@ func TestProofWithDeletion(t *testing.T) { // notice the sibling of key `k*32`` is just the leaf of key `m*32` assert.Equal(t, siblings[0][l-33:l-1], nd) - // no effect + // Marking a key that is currently not hit (but terminated by an empty node) + // also causes it to be added to the deletion proof proofTracer.MarkDeletion(s_key2.Bytes()) siblings, err = proofTracer.GetDeletionProofs() assert.NoError(t, err) - assert.Equal(t, 1, len(siblings)) + assert.Equal(t, 2, len(siblings)) key3 := bytes.Repeat([]byte("x"), 32) err = mt.UpdateWord( diff --git a/trie/zktrie_deletionproof.go b/trie/zktrie_deletionproof.go index ebb8419e7..8547c1eda 100644 --- a/trie/zktrie_deletionproof.go +++ b/trie/zktrie_deletionproof.go @@ -28,6 +28,7 @@ type ProofTracer struct { *ZkTrie deletionTracer map[zkt.Hash]struct{} rawPaths map[string][]*zktrie.Node + emptyTermPaths map[string][]*zktrie.Node } // NewProofTracer create a proof tracer object @@ -37,6 +38,7 @@ func (t *ZkTrie) NewProofTracer() *ProofTracer { // always consider 0 is "deleted" deletionTracer: map[zkt.Hash]struct{}{zkt.HashZero: {}}, rawPaths: make(map[string][]*zktrie.Node), + emptyTermPaths: make(map[string][]*zktrie.Node), } } @@ -112,9 +114,13 @@ func (t *ProofTracer) GetDeletionProofs() ([][]byte, error) { // MarkDeletion mark a key has been involved into deletion func (t *ProofTracer) MarkDeletion(key []byte) { - if path, existed := t.rawPaths[string(key)]; existed { + if path, existed := t.emptyTermPaths[string(key)]; existed { + // copy empty node terminated path for final scanning + t.rawPaths[string(key)] = path + } else if path, existed = t.rawPaths[string(key)]; existed { // sanity check leafNode := path[len(path)-1] + if leafNode.Type != zktrie.NodeTypeLeaf { panic("all path recorded in proofTrace should be ended with leafNode") } @@ -143,6 +149,11 @@ func (t *ProofTracer) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWr } } else if n.Type == zktrie.NodeTypeParent { mptPath = append(mptPath, n) + } else if n.Type == zktrie.NodeTypeEmpty { + // empty node is considered as "unhit" but it should be also being added + // into a temporary slot for possibly being marked as deletion later + mptPath = append(mptPath, n) + t.emptyTermPaths[string(key)] = mptPath } return proofDb.Put(nodeHash[:], n.Value()) From a89ab83089702816d4834d95cde1f2aacfa41b98 Mon Sep 17 00:00:00 2001 From: maskpp Date: Mon, 22 May 2023 16:52:13 +0800 Subject: [PATCH 04/22] fix(API): use `hexutil.Big` for `l1Fee` in `GetTransactionReceipt` (#336) * It's not a bug, but if just translate to hexutil.Big can be better. * Revert editor's auto change. * Update version. * Update version. --- internal/ethapi/api.go | 2 +- params/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index d6e2591a1..0f9fe5049 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1669,7 +1669,7 @@ func (s *PublicTransactionPoolAPI) GetTransactionReceipt(ctx context.Context, ha "logs": receipt.Logs, "logsBloom": receipt.Bloom, "type": hexutil.Uint(tx.Type()), - "l1Fee": hexutil.Uint64(receipt.L1Fee.Uint64()), + "l1Fee": (*hexutil.Big)(receipt.L1Fee), } // Assign the effective gas price paid if !s.b.ChainConfig().IsLondon(bigblock) { diff --git a/params/version.go b/params/version.go index ce0873da8..1e971462f 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release + VersionPatch = 4 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) From 2f7085715bc20fcffb9b34d4aa71ea56fca1dd65 Mon Sep 17 00:00:00 2001 From: Richord Date: Mon, 22 May 2023 07:37:27 -0700 Subject: [PATCH 05/22] refactor(config): moved fee vault addr to rollup config (#341) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * moved fee vault addr to rollup config * fixed linter issue * added back FeeVaultAddress --------- Co-authored-by: Péter Garamvölgyi --- params/config.go | 4 ++-- rollup/rcfg/config.go | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/params/config.go b/params/config.go index 4450c250f..acef5214f 100644 --- a/params/config.go +++ b/params/config.go @@ -24,6 +24,7 @@ import ( "golang.org/x/crypto/sha3" "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/rollup/rcfg" ) // Genesis hashes to enforce below configs on. @@ -255,7 +256,6 @@ var ( } // ScrollAlphaChainConfig contains the chain parameters to run a node on the Scroll Alpha test network. - ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005") ScrollMaxTxPerBlock = 44 ScrollMaxTxPayloadBytesPerBlock = 120 * 1024 @@ -285,7 +285,7 @@ var ( UseZktrie: true, MaxTxPerBlock: &ScrollMaxTxPerBlock, MaxTxPayloadBytesPerBlock: &ScrollMaxTxPayloadBytesPerBlock, - FeeVaultAddress: &ScrollFeeVaultAddress, + FeeVaultAddress: &rcfg.ScrollFeeVaultAddress, EnableEIP2718: false, EnableEIP1559: false, }, diff --git a/rollup/rcfg/config.go b/rollup/rcfg/config.go index 3d2bd54e6..3e0e00ce0 100644 --- a/rollup/rcfg/config.go +++ b/rollup/rcfg/config.go @@ -16,6 +16,11 @@ var ( L2MessageQueueAddress = common.HexToAddress("0x5300000000000000000000000000000000000000") WithdrawTrieRootSlot = common.BigToHash(big.NewInt(0)) + // ScrollFeeVaultAddress is the address of the L2TxFeeVault + // predeploy + // see scroll-tech/scroll/contracts/src/L2/predeploys/L2TxFeeVault.sol + ScrollFeeVaultAddress = common.HexToAddress("0x5300000000000000000000000000000000000005") + // L1GasPriceOracleAddress is the address of the L1GasPriceOracle // predeploy // see scroll-tech/scroll/contracts/src/L2/predeploys/L1GasPriceOracle.sol From 0c182f13bddffb6327adb7511b547436fd80c578 Mon Sep 17 00:00:00 2001 From: maskpp Date: Mon, 22 May 2023 22:44:05 +0800 Subject: [PATCH 06/22] feat(abigen): add `--contract` flag to specify a given contract (#334) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add contract flag * Use GlobalXX * Update cmd/abigen/main.go Co-authored-by: Péter Garamvölgyi * Update cmd/abigen/main.go Co-authored-by: Péter Garamvölgyi --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- cmd/abigen/main.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 24b3f12bc..0c6926f5f 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -99,6 +99,10 @@ var ( Name: "alias", Usage: "Comma separated aliases for function and event renaming, e.g. original1=alias1, original2=alias2", } + contractFlag = cli.StringFlag{ + Name: "contract", + Usage: "Name of the contract to generate the bindings for", + } ) func init() { @@ -117,6 +121,7 @@ func init() { outFlag, langFlag, aliasFlag, + contractFlag, } app.Action = utils.MigrateFlags(abigen) cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate @@ -225,6 +230,13 @@ func abigen(c *cli.Context) error { } // Gather all non-excluded contract for binding for name, contract := range contracts { + // The fully qualified name is of the form : + nameParts := strings.Split(name, ":") + typeName := nameParts[len(nameParts)-1] + // If a contract name is provided then ignore all other contracts + if c.GlobalIsSet(contractFlag.Name) && c.GlobalString(contractFlag.Name) != typeName { + continue + } if exclude[strings.ToLower(name)] { continue } @@ -235,11 +247,10 @@ func abigen(c *cli.Context) error { abis = append(abis, string(abi)) bins = append(bins, contract.Code) sigs = append(sigs, contract.Hashes) - nameParts := strings.Split(name, ":") - types = append(types, nameParts[len(nameParts)-1]) + types = append(types, typeName) libPattern := crypto.Keccak256Hash([]byte(name)).String()[2:36] - libs[libPattern] = nameParts[len(nameParts)-1] + libs[libPattern] = typeName } } // Extract all aliases from the flags From 9f981c57131c338227ffc49bbd58d1fa45223602 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Wed, 24 May 2023 08:15:06 +0800 Subject: [PATCH 07/22] build(github): update github action yaml (#347) * build(github): update github action yaml * fix --- .github/workflows/docker.yaml | 2 +- .github/workflows/l2geth_ci.yml | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 6ba74507b..17516dd12 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -3,7 +3,7 @@ name: Docker on: push: tags: - - scroll-v** + - '*' jobs: build-and-push: diff --git a/.github/workflows/l2geth_ci.yml b/.github/workflows/l2geth_ci.yml index 3722ac70d..e840ecf9a 100644 --- a/.github/workflows/l2geth_ci.yml +++ b/.github/workflows/l2geth_ci.yml @@ -6,9 +6,15 @@ on: - develop - alpha pull_request: + types: + - opened + - reopened + - synchronize + - ready_for_review name: CI jobs: build: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Install Go @@ -21,6 +27,7 @@ jobs: run: | make geth check: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Install Go @@ -34,6 +41,7 @@ jobs: rm -rf $HOME/.cache/golangci-lint make lint goimports-lint: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Install Go @@ -53,6 +61,7 @@ jobs: exit 1 fi test: + if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Install Go From fb570dc8c7c115844fa4d2e9f2fac61a981e8bb8 Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Wed, 24 May 2023 14:50:37 +0800 Subject: [PATCH 08/22] ci(github): update pull request template (#349) * chore(github): update pull request template * Update pull_request_template.md --- .github/pull_request_template.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index cc4474426..e191fb898 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -3,10 +3,32 @@ ... -## 2. Deployment tag versioning +## 2. PR title + +Your PR title must follow [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) (as we are doing squash merge for each PR), so it must start with one of the following [types](https://github.com/angular/angular/blob/22b96b9/CONTRIBUTING.md#type): + +- [ ] build: Changes that affect the build system or external dependencies (example scopes: yarn, eslint, typescript) +- [ ] ci: Changes to our CI configuration files and scripts (example scopes: vercel, github, cypress) +- [ ] docs: Documentation-only changes +- [ ] feat: A new feature +- [ ] fix: A bug fix +- [ ] perf: A code change that improves performance +- [ ] refactor: A code change that doesn't fix a bug, or add a feature, or improves performance +- [ ] style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) +- [ ] test: Adding missing tests or correcting existing tests + + +## 3. Deployment tag versioning Has the version in `params/version.go` been updated? - [ ] This PR doesn't involve a new deployment, git tag, docker image tag, and it doesn't affect traces - [ ] Yes + +## 4. Breaking change label + +Does this PR have the `breaking-change` label? + +- [ ] This PR is not a breaking change +- [ ] Yes From 334892ae078dbc0ac7103a78bc29cdc83e1151cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 24 May 2023 11:41:33 +0200 Subject: [PATCH 09/22] Return error for disabled precompile calls (#337) return error for disabled precompile calls --- core/vm/contracts.go | 34 ++++++++++++++++++++++++++++++++++ params/version.go | 4 ++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 5bc67a3a6..6a951cce1 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -34,6 +34,10 @@ import ( "golang.org/x/crypto/ripemd160" ) +var ( + errPrecompileDisabled = errors.New("sha256, ripemd160, blake2f precompiles temporarily disabled") +) + // PrecompiledContract is the basic interface for native Go contracts. The implementation // requires a deterministic gas count based on the input size of the Run method of the // contract. @@ -96,11 +100,14 @@ var PrecompiledContractsBerlin = map[common.Address]PrecompiledContract{ // contracts used in the Archimedes release. Same as Berlin but without sha2, blake2f, ripemd160 var PrecompiledContractsArchimedes = map[common.Address]PrecompiledContract{ common.BytesToAddress([]byte{1}): &ecrecover{}, + common.BytesToAddress([]byte{2}): &sha256hashDisabled{}, + 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{}, } // PrecompiledContractsBLS contains the set of pre-compiled Ethereum @@ -227,6 +234,15 @@ func (c *sha256hash) Run(input []byte) ([]byte, error) { return h[:], nil } +type sha256hashDisabled struct{} + +func (c *sha256hashDisabled) RequiredGas(input []byte) uint64 { + return (&sha256hash{}).RequiredGas(input) +} +func (c *sha256hashDisabled) Run(input []byte) ([]byte, error) { + return nil, errPrecompileDisabled +} + // RIPEMD160 implemented as a native contract. type ripemd160hash struct{} @@ -243,6 +259,15 @@ func (c *ripemd160hash) Run(input []byte) ([]byte, error) { return common.LeftPadBytes(ripemd.Sum(nil), 32), nil } +type ripemd160hashDisabled struct{} + +func (c *ripemd160hashDisabled) RequiredGas(input []byte) uint64 { + return (&ripemd160hash{}).RequiredGas(input) +} +func (c *ripemd160hashDisabled) Run(input []byte) ([]byte, error) { + return nil, errPrecompileDisabled +} + // data copy implemented as a native contract. type dataCopy struct{} @@ -636,6 +661,15 @@ func (c *blake2F) Run(input []byte) ([]byte, error) { return output, nil } +type blake2FDisabled struct{} + +func (c *blake2FDisabled) RequiredGas(input []byte) uint64 { + return (&blake2F{}).RequiredGas(input) +} +func (c *blake2FDisabled) Run(input []byte) ([]byte, error) { + return nil, errPrecompileDisabled +} + var ( errBLS12381InvalidInputLength = errors.New("invalid input length") errBLS12381InvalidFieldElementTopBytes = errors.New("invalid field element top bytes") diff --git a/params/version.go b/params/version.go index 1e971462f..7c98147ce 100644 --- a/params/version.go +++ b/params/version.go @@ -23,8 +23,8 @@ import ( const ( VersionMajor = 3 // Major version component of the current release - VersionMinor = 2 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release + VersionMinor = 3 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) From 060815e901c34649446fd17be99a7af5e9091fd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 24 May 2023 14:10:42 +0200 Subject: [PATCH 10/22] feat: delay Archimedes on Alpha by a week (#342) * delay Archimedes on Alpha by a week * bump version --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- params/config.go | 2 +- params/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/params/config.go b/params/config.go index acef5214f..df257a60e 100644 --- a/params/config.go +++ b/params/config.go @@ -275,7 +275,7 @@ var ( BerlinBlock: big.NewInt(0), LondonBlock: big.NewInt(0), ArrowGlacierBlock: nil, - ArchimedesBlock: big.NewInt(2444711), + ArchimedesBlock: big.NewInt(2646311), ShanghaiBlock: nil, Clique: &CliqueConfig{ Period: 3, diff --git a/params/version.go b/params/version.go index 7c98147ce..fba8c3a42 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 3 // Major version component of the current release VersionMinor = 3 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "alpha" // Version metadata to append to the version string ) From 31b754e14bd5416f5a62a4633b46a21add90a1f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 24 May 2023 14:22:54 +0200 Subject: [PATCH 11/22] Fix transaction DA cost under-estimation (#332) * fix tx DA cost under-estimation * bump version --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- params/version.go | 8 ++++---- rollup/fees/rollup_fee.go | 11 ++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/params/version.go b/params/version.go index fba8c3a42..9c9e0b65c 100644 --- a/params/version.go +++ b/params/version.go @@ -22,10 +22,10 @@ import ( ) const ( - VersionMajor = 3 // Major version component of the current release - VersionMinor = 3 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release - VersionMeta = "alpha" // Version metadata to append to the version string + VersionMajor = 4 // Major version component of the current release + VersionMinor = 0 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "sepolia" // Version metadata to append to the version string ) // Version holds the textual version string. diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index 32c8439b5..dd2a726f0 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -17,6 +17,15 @@ var ( // transaction to the L1 fee calculation routine. The signature is accounted // for externally errTransactionSigned = errors.New("transaction is signed") + + // txExtraDataBytes is the number of bytes that we commit to L1 in addition + // to the RLP-encoded unsigned transaction. Note that these are all assumed + // to be non-zero. + // - tx length prefix: 4 bytes + // - sig.r: 32 bytes + 1 byte rlp prefix + // - sig.s: 32 bytes + 1 byte rlp prefix + // - sig.v: 3 bytes + 1 byte rlp prefix + txExtraDataBytes = uint64(74) ) // Message represents the interface of a message. @@ -115,7 +124,7 @@ func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) func CalculateL1GasUsed(data []byte, overhead *big.Int) *big.Int { zeroes, ones := zeroesAndOnes(data) zeroesGas := zeroes * params.TxDataZeroGas - onesGas := (ones + 68) * params.TxDataNonZeroGasEIP2028 + onesGas := (ones + txExtraDataBytes) * params.TxDataNonZeroGasEIP2028 l1Gas := new(big.Int).SetUint64(zeroesGas + onesGas) return new(big.Int).Add(l1Gas, overhead) } From ad43d0ea3fca425636d7478d8f6e1a215385058d Mon Sep 17 00:00:00 2001 From: Richord Date: Wed, 24 May 2023 06:23:57 -0700 Subject: [PATCH 12/22] feat(block_validator): check payload size during block validation (#322) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added block size check in validate body * added payload method to block * Update core/types/block.go Co-authored-by: Péter Garamvölgyi * fixed test * fix * bump version * fix test --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Péter Garamvölgyi --- core/block_validator.go | 4 +++ core/blockchain_test.go | 61 +++++++++++++++++++++++++++++++++++++++++ core/error.go | 3 ++ core/types/block.go | 10 +++++++ params/version.go | 2 +- 5 files changed, 79 insertions(+), 1 deletion(-) diff --git a/core/block_validator.go b/core/block_validator.go index 393abb3f6..4a0a59d75 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -57,6 +57,10 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if !v.config.Scroll.IsValidTxCount(len(block.Transactions())) { return consensus.ErrInvalidTxCount } + // Check if block payload size is smaller than the max size + if !v.config.Scroll.IsValidBlockSize(block.PayloadSize()) { + return ErrInvalidBlockPayloadSize + } // Header validity is known at this point, check the uncles and transactions header := block.Header() if err := v.engine.VerifyUncles(v.bc, block); err != nil { diff --git a/core/blockchain_test.go b/core/blockchain_test.go index abf633220..ab149d15a 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3239,6 +3239,67 @@ func TestTransactionCountLimit(t *testing.T) { } } +func TestBlockPayloadSizeLimit(t *testing.T) { + // Create config that allows at most 150 bytes per block payload + config := params.TestChainConfig + config.Scroll.MaxTxPayloadBytesPerBlock = new(int) + *config.Scroll.MaxTxPayloadBytesPerBlock = 150 + config.Scroll.MaxTxPerBlock = nil + + var ( + engine = ethash.NewFaker() + db = rawdb.NewMemoryDatabase() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + address = crypto.PubkeyToAddress(key.PublicKey) + funds = big.NewInt(1000000000000000) + gspec = &Genesis{Config: config, Alloc: GenesisAlloc{address: {Balance: funds}}} + genesis = gspec.MustCommit(db) + ) + + addTx := func(b *BlockGen) { + tx := types.NewTransaction(b.TxNonce(address), address, big.NewInt(0), 50000, b.header.BaseFee, nil) + signed, _ := types.SignTx(tx, types.HomesteadSigner{}, key) + b.AddTx(signed) + } + + // Initialize blockchain + blockchain, err := NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil) + if err != nil { + t.Fatalf("failed to create new chain manager: %v", err) + } + defer blockchain.Stop() + + // Insert empty block + block1, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) { + // empty + }) + + if _, err := blockchain.InsertChain(block1); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + // Insert block with 1 transaction + block2, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) { + addTx(b) + }) + + if _, err := blockchain.InsertChain(block2); err != nil { + t.Fatalf("failed to insert chain: %v", err) + } + + // Insert block with 2 transactions + block3, _ := GenerateChain(config, genesis, ethash.NewFaker(), db, 1, func(i int, b *BlockGen) { + addTx(b) + addTx(b) + }) + + _, err = blockchain.InsertChain(block3) + + if !errors.Is(err, ErrInvalidBlockPayloadSize) { + t.Fatalf("error mismatch: have: %v, want: %v", err, ErrInvalidBlockPayloadSize) + } +} + func TestEIP3651(t *testing.T) { var ( addraa = common.HexToAddress("0x000000000000000000000000000000000000aaaa") diff --git a/core/error.go b/core/error.go index 40ebd9773..a29f56ebf 100644 --- a/core/error.go +++ b/core/error.go @@ -26,6 +26,9 @@ var ( // ErrKnownBlock is returned when a block to import is already known locally. ErrKnownBlock = errors.New("block already known") + // ErrInvalidBlockPayloadSize is returned when a block to import has an oversized payload. + ErrInvalidBlockPayloadSize = errors.New("invalid block payload size") + // ErrBannedHash is returned if a block to import is on the banned list. ErrBannedHash = errors.New("banned hash") diff --git a/core/types/block.go b/core/types/block.go index 78f40bf0b..1c6571f4e 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -325,6 +325,16 @@ func (b *Block) Size() common.StorageSize { return common.StorageSize(c) } +// PayloadSize returns the sum of all transactions in a block. +func (b *Block) PayloadSize() common.StorageSize { + // add up all txs sizes + var totalSize common.StorageSize + for _, tx := range b.transactions { + totalSize += tx.Size() + } + return totalSize +} + // SanityCheck can be used to prevent that unbounded fields are // stuffed with junk data to add processing overhead func (b *Block) SanityCheck() error { diff --git a/params/version.go b/params/version.go index 9c9e0b65c..2d6b30959 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 0 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) From 74725fbe2c025d1a2b2314cf0a4670457743ca60 Mon Sep 17 00:00:00 2001 From: colin <102356659+colinlyguo@users.noreply.github.com> Date: Thu, 25 May 2023 16:30:50 +0800 Subject: [PATCH 13/22] test(zkTrie): add deletion test in account update unit test (#344) feat(zkTrie): extend account update unit tests with account deletion --- trie/zk_trie_impl_test.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/trie/zk_trie_impl_test.go b/trie/zk_trie_impl_test.go index c42ab8c05..9710b6b4f 100644 --- a/trie/zk_trie_impl_test.go +++ b/trie/zk_trie_impl_test.go @@ -86,6 +86,10 @@ func (mt *zkTrieImplTestWrapper) TryGet(key []byte) ([]byte, error) { return mt.ZkTrieImpl.TryGet(zkt.NewHashFromBytes(key)) } +func (mt *zkTrieImplTestWrapper) TryDelete(key []byte) error { + return mt.ZkTrieImpl.TryDelete(zkt.NewHashFromBytes(key)) +} + // TryUpdateAccount will abstract the write of an account to the trie func (mt *zkTrieImplTestWrapper) TryUpdateAccount(key []byte, acc *types.StateAccount) error { value, flag := acc.MarshalFields() @@ -271,4 +275,18 @@ func TestMerkleTree_UpdateAccount(t *testing.T) { bt, err = mt.TryGet(common.HexToAddress("0x8dE13967F19410A7991D63c2c0179feBFDA0c261").Bytes()) assert.Nil(t, err) assert.Nil(t, bt) + + err = mt.TryDelete(common.HexToHash("0x05fDbDfaE180345C6Cff5316c286727CF1a43327").Bytes()) + assert.Nil(t, err) + + bt, err = mt.TryGet(common.HexToAddress("0x05fDbDfaE180345C6Cff5316c286727CF1a43327").Bytes()) + assert.Nil(t, err) + assert.Nil(t, bt) + + err = mt.TryDelete(common.HexToAddress("0x4cb1aB63aF5D8931Ce09673EbD8ae2ce16fD6571").Bytes()) + assert.Nil(t, err) + + bt, err = mt.TryGet(common.HexToAddress("0x4cb1aB63aF5D8931Ce09673EbD8ae2ce16fD6571").Bytes()) + assert.Nil(t, err) + assert.Nil(t, bt) } From 9b943f3e0be92373f8c3d04245b1cc474645d56b Mon Sep 17 00:00:00 2001 From: Ho Date: Thu, 1 Jun 2023 17:00:37 +0800 Subject: [PATCH 14/22] fix: add missing term when merging two deletion proofs (#353) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: merge emptyTermPaths * bump version --------- Co-authored-by: Péter Garamvölgyi --- params/version.go | 2 +- trie/zktrie_deletionproof.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/params/version.go b/params/version.go index 2d6b30959..adaaf1b52 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 0 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionPatch = 2 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) diff --git a/trie/zktrie_deletionproof.go b/trie/zktrie_deletionproof.go index 8547c1eda..e350e5063 100644 --- a/trie/zktrie_deletionproof.go +++ b/trie/zktrie_deletionproof.go @@ -58,6 +58,10 @@ func (t *ProofTracer) Merge(another *ProofTracer) *ProofTracer { t.rawPaths[k] = v } + for k, v := range another.emptyTermPaths { + t.emptyTermPaths[k] = v + } + return t } From 1f167bd730c57af18ae25c25bcb7a255de048d05 Mon Sep 17 00:00:00 2001 From: Richord Date: Mon, 5 Jun 2023 09:52:46 -0700 Subject: [PATCH 15/22] fix(ethclient): support WithdrawalsHash in Scroll Go SDK (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added withdrawalsHash to header * bump version --------- Co-authored-by: Péter Garamvölgyi --- core/types/block.go | 4 ++ core/types/gen_header_json.go | 72 +++++++++++++++++++---------------- params/version.go | 2 +- 3 files changed, 44 insertions(+), 34 deletions(-) diff --git a/core/types/block.go b/core/types/block.go index 1c6571f4e..b8e44344f 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -85,6 +85,10 @@ type Header struct { // BaseFee was added by EIP-1559 and is ignored in legacy headers. BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"` + + // WithdrawalsHash was added by EIP-4895 and is ignored in legacy headers. + // Included for Ethereum compatibility in Scroll SDK + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } // field type overrides for gencodec diff --git a/core/types/gen_header_json.go b/core/types/gen_header_json.go index 01f812dda..d40c0e138 100644 --- a/core/types/gen_header_json.go +++ b/core/types/gen_header_json.go @@ -16,23 +16,24 @@ var _ = (*headerMarshaling)(nil) // MarshalJSON marshals as JSON. func (h Header) MarshalJSON() ([]byte, error) { type Header struct { - ParentHash common.Hash `json:"parentHash" gencodec:"required"` - UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase common.Address `json:"miner" gencodec:"required"` - Root common.Hash `json:"stateRoot" gencodec:"required"` - TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` - Hash common.Hash `json:"hash"` + ParentHash common.Hash `json:"parentHash" gencodec:"required"` + UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase common.Address `json:"miner" gencodec:"required"` + Root common.Hash `json:"stateRoot" gencodec:"required"` + TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest common.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` + Hash common.Hash `json:"hash"` } var enc Header enc.ParentHash = h.ParentHash @@ -51,6 +52,7 @@ func (h Header) MarshalJSON() ([]byte, error) { enc.MixDigest = h.MixDigest enc.Nonce = h.Nonce enc.BaseFee = (*hexutil.Big)(h.BaseFee) + enc.WithdrawalsHash = h.WithdrawalsHash enc.Hash = h.Hash() return json.Marshal(&enc) } @@ -58,22 +60,23 @@ func (h Header) MarshalJSON() ([]byte, error) { // UnmarshalJSON unmarshals from JSON. func (h *Header) UnmarshalJSON(input []byte) error { type Header struct { - ParentHash *common.Hash `json:"parentHash" gencodec:"required"` - UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` - Coinbase *common.Address `json:"miner" gencodec:"required"` - Root *common.Hash `json:"stateRoot" gencodec:"required"` - TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` - ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` - Bloom *Bloom `json:"logsBloom" gencodec:"required"` - Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` - Number *hexutil.Big `json:"number" gencodec:"required"` - GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` - Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` - MixDigest *common.Hash `json:"mixHash"` - Nonce *BlockNonce `json:"nonce"` - BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + ParentHash *common.Hash `json:"parentHash" gencodec:"required"` + UncleHash *common.Hash `json:"sha3Uncles" gencodec:"required"` + Coinbase *common.Address `json:"miner" gencodec:"required"` + Root *common.Hash `json:"stateRoot" gencodec:"required"` + TxHash *common.Hash `json:"transactionsRoot" gencodec:"required"` + ReceiptHash *common.Hash `json:"receiptsRoot" gencodec:"required"` + Bloom *Bloom `json:"logsBloom" gencodec:"required"` + Difficulty *hexutil.Big `json:"difficulty" gencodec:"required"` + Number *hexutil.Big `json:"number" gencodec:"required"` + GasLimit *hexutil.Uint64 `json:"gasLimit" gencodec:"required"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` + Time *hexutil.Uint64 `json:"timestamp" gencodec:"required"` + Extra *hexutil.Bytes `json:"extraData" gencodec:"required"` + MixDigest *common.Hash `json:"mixHash"` + Nonce *BlockNonce `json:"nonce"` + BaseFee *hexutil.Big `json:"baseFeePerGas" rlp:"optional"` + WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"optional"` } var dec Header if err := json.Unmarshal(input, &dec); err != nil { @@ -140,5 +143,8 @@ func (h *Header) UnmarshalJSON(input []byte) error { if dec.BaseFee != nil { h.BaseFee = (*big.Int)(dec.BaseFee) } + if dec.WithdrawalsHash != nil { + h.WithdrawalsHash = dec.WithdrawalsHash + } return nil } diff --git a/params/version.go b/params/version.go index adaaf1b52..0bb4230e4 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 0 // Minor version component of the current release - VersionPatch = 2 // Patch version component of the current release + VersionPatch = 3 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) From 4867699afa29ecf900bf65221e26b5655ebbc539 Mon Sep 17 00:00:00 2001 From: Ho Date: Wed, 7 Jun 2023 09:16:45 +0800 Subject: [PATCH 16/22] fix(tracing): fix ZktrieTracer race condition (#356) * fix race condition of zktrie tracer * Update version.go --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- eth/tracers/api_blocktrace.go | 2 +- params/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 030cabe01..0a9a1ab44 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -345,7 +345,7 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc m = make(map[string][]hexutil.Bytes) env.StorageProofs[addrStr] = m if zktrieTracer.Available() { - env.zkTrieTracer[addrStr] = zktrieTracer + env.zkTrieTracer[addrStr] = state.NewProofTracer(trie) } } else if _, existed := m[keyStr]; existed { // still need to touch tracer for deletion diff --git a/params/version.go b/params/version.go index 0bb4230e4..94a96e6d6 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 0 // Minor version component of the current release - VersionPatch = 3 // Patch version component of the current release + VersionPatch = 4 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) From 983d6302443376345cbcd66b98382ef94a30f9e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Wed, 7 Jun 2023 16:24:19 +0200 Subject: [PATCH 17/22] feat: Sync and relay L1 messages (#350) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add l1 config in genesis config (#249) * add l1 config in genesis config * fix lint * Update params/config.go Co-authored-by: Péter Garamvölgyi --------- Co-authored-by: Péter Garamvölgyi * extend node configuration (#251) * extend node configuration * use block number instead of hash * accept safe, finalized and numbers for L1Confirmations * fix typos --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Péter Garamvölgyi * Fix/improve node config parsing (#260) * raise error on failed parsing * default value * add l1-message-type, transaction methods (#252) * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * txpool l1 check, pointer change, marhsal test * draft: start implementing l1message gas behavior * draft: start implementing l1message gas behavior * change to gas usage * error comment typo Co-authored-by: Haichen Shen * goimports * update nonce, add hash test (fails), marshal test * goimports * target addr cant be nil * change call msg * comment out test * lint --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Haichen Shen * Add L1 message database (#255) * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * add L1 message store to rawdb * remove comments * rename to l1_message * rename variables and add comments * write l1 msgs in a batch * add more comments * update tests * allow batched and non-batched writes * rename to accessors_l1_message * handle error * add range check * fix tests * update comments * nit * support blocks with 0 l1 messages --------- Co-authored-by: Max Wolff Co-authored-by: Max Wolff * Fix L1Message Deep Copy, Complete Bridge Tx Hash test (#269) * deep copy value field, add tx hash test comment * typo * Rename nonce to queueindex, increment sender nonce on L1 message execution (#271) * change nonce to queueindex, increment nonce on L1 message * fix db acccessors * Update core/types/transaction_marshalling.go Co-authored-by: Péter Garamvölgyi --------- Co-authored-by: Péter Garamvölgyi * Fix db inspect command (#276) fix db inspect command * Add l1 sync service (#256) * extend node configuration * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * use block number instead of hash * accept safe, finalized and numbers for L1Confirmations * add L1 message store to rawdb * remove comments * fix typos * add L1 message sync service * use l1 contract address and chain ID * use L1DeploymentBlock * add confirmation config * move bridge client to separate file * use uint64 block number * fix bigint comparison * rename constants * add more logs * rename to l1_message * rename variables and add comments * write l1 msgs in a batch * add more comments * update tests * allow batched and non-batched writes * rename to accessors_l1_message * handle error * check if config is provided * improve sync service DB batched writes * add range check * fix tests * update comments * nit * fix flush range and improve comments * solve circular dependency * update stress tests * initialize l1 client for geth * start sync service * add more comments * check nil correctly * address comments * fix merge * fix genesis l1config deserialization * add sync progress logs * initial sync * handle leveldb not found error * use errors.Is * address comments * update DefaultPollInterval --------- Co-authored-by: Nazarii Denha Co-authored-by: Max Wolff Co-authored-by: Max Wolff * Add L1 message validation (#272) * add L1 message validation * add comments and better error handling * handle leveldb not found error * update incorrect condition for genesis block * typo * change inclusion index logic * disable L1 message check for legacy tests * set NumL1MessagesPerBlock to 0 in tests * update default genesis config * Add L1 msg validation tests (#303) add L1 msg validation tests * Update miner include l1 messages (#265) * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * add L1 message store to rawdb * add L1 message sync service * remove comments * use l1 contract address and chain ID * extend node configuration * use block number instead of hash * accept safe, finalized and numbers for L1Confirmations * fix typos * use L1DeploymentBlock * add confirmation config * move bridge client to separate file * use uint64 block number * fix bigint comparison * rename constants * add more logs * Fix/improve node config parsing (#260) * raise error on failed parsing * default value * rename to l1_message * rename variables and add comments * write l1 msgs in a batch * add more comments * update tests * allow batched and non-batched writes * rename to accessors_l1_message * handle error * check if config is provided * improve sync service DB batched writes * include l1 messages in blocks: part 1 * add l1-message-type, transaction methods (#252) * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * txpool l1 check, pointer change, marhsal test * draft: start implementing l1message gas behavior * draft: start implementing l1message gas behavior * change to gas usage * error comment typo Co-authored-by: Haichen Shen * goimports * update nonce, add hash test (fails), marshal test * goimports * target addr cant be nil * change call msg * comment out test * lint --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Haichen Shen * Add L1 message database (#255) * add l1-message-type, transaction methods * goimports * Update core/types/transaction.go Co-authored-by: Péter Garamvölgyi * add L1 message store to rawdb * remove comments * rename to l1_message * rename variables and add comments * write l1 msgs in a batch * add more comments * update tests * allow batched and non-batched writes * rename to accessors_l1_message * handle error * add range check * fix tests * update comments * nit * support blocks with 0 l1 messages --------- Co-authored-by: Max Wolff Co-authored-by: Max Wolff * build(docker): auto docker push when pushing git tags (#258) * build(docker): update docker trigger tag prefix (#259) * Fix L1Message Deep Copy, Complete Bridge Tx Hash test (#269) * deep copy value field, add tx hash test comment * typo * commitl1messages * lint * Revert "add L1 message sync service" This reverts commit 5305e8a5de14766ed249e1a7d64042c7a72cf5c2. * Revert "move bridge client to separate file" This reverts commit 0b220bee37de93c3250545e23430db2c401a2f90. * update branch * use commitMessages for l1Txs * little fix * fix config * fix test * comment fixes * fix * fix config check --------- Co-authored-by: Max Wolff Co-authored-by: Max Wolff Co-authored-by: Péter Garamvölgyi Co-authored-by: Péter Garamvölgyi Co-authored-by: Haichen Shen Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> * Add ErrUnknownAncestor tests (#305) add ErrUnknownAncestor tests * worker test include l1 msgs (#306) * worker test include l1 msgs * move L1 message index update next to block insertion --------- Co-authored-by: Péter Garamvölgyi * exclude l1 messages from transaction count limit in block (#307) * exclude l1 messages from transaction count limit in block * fix comments * trigger ci * nit --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Péter Garamvölgyi * Expose queueIndex on Transaction (#316) expose queueIndex on Transaction * test that l1msg doesn't count in maxTxPerBlock limit (#312) * test that l1msg doesn't count in maxTxPerBlock limit * fix, comment * retrigger ci * change order inside test --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Co-authored-by: Péter Garamvölgyi * reuse trace nonce field for queueIndex * expose scroll APIs on the geth console * add L1 message query APIs * Trigger new block on new l1 messages (#343) * trigger new block on new l1 messages * typo * initialize l1MsgCh * fix worker l1msg tests (#345) --------- Co-authored-by: Nazarii Denha * test(worker): ensure that l1 messages are included in the correct order (#346) test that l1msgs added in correct order * rename enqueueIndex --> queueIndex * move QueueIndex into transaction * improve l1 db interface * formatting * bump version * print l1config * add API to query latest included message queue index * clean up tx limit logic * add clarifying comments and todos to ValidateL1Messages * improve db comments and logs * clean up L1MessageTx type handling * format * format * improve L1 message block check * fix missing L1 event handling * fix TestL1MessageValidationFailure * simplify sync height resume logic * make l1Config.l1MessageQueueAddress non-pointer * improve command line flags * remove todo * use abigen tools for log filtering * cache block L1 message count * nit: fix variable name case * improve logs * flush pending writes to DB before shutdown --------- Co-authored-by: Nazarii Denha Co-authored-by: Max Wolff Co-authored-by: Haichen Shen Co-authored-by: Max Wolff Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Co-authored-by: HAOYUatHZ --- accounts/abi/bind/backends/simulated.go | 1 + cmd/devp2p/internal/ethtest/suite_test.go | 2 +- cmd/geth/main.go | 3 + cmd/utils/flags.go | 73 ++++++- consensus/errors.go | 15 ++ console/console_test.go | 2 +- core/block_validator.go | 70 ++++++- core/blockchain.go | 26 ++- core/blockchain_test.go | 162 +++++++++++++++ core/events.go | 3 + core/genesis.go | 1 + core/rawdb/accessors_l1_message.go | 211 ++++++++++++++++++++ core/rawdb/accessors_l1_message_test.go | 127 ++++++++++++ core/rawdb/database.go | 10 +- core/rawdb/schema.go | 22 +++ core/state_transition.go | 19 ++ core/tx_pool.go | 5 + core/types/block.go | 25 ++- core/types/l1_message_tx.go | 54 +++++ core/types/l2trace.go | 8 +- core/types/receipt.go | 3 + core/types/transaction.go | 99 +++++++--- core/types/transaction_marshalling.go | 34 ++++ core/types/transaction_signing.go | 11 ++ core/types/transaction_test.go | 47 ++++- eth/api.go | 40 ++++ eth/backend.go | 41 ++-- eth/catalyst/api_test.go | 2 +- eth/handler.go | 4 + eth/sync_test.go | 16 +- ethclient/ethclient_test.go | 2 +- ethclient/gethclient/gethclient_test.go | 2 +- ethdb/memorydb/memorydb.go | 6 +- graphql/graphql_test.go | 4 +- internal/web3ext/web3ext.go | 35 ++++ les/api_test.go | 2 +- miner/miner.go | 4 + miner/miner_test.go | 24 ++- miner/stress/1559/main.go | 2 +- miner/stress/clique/main.go | 2 +- miner/stress/ethash/main.go | 2 +- miner/worker.go | 79 +++++++- miner/worker_test.go | 210 +++++++++++++++++++- node/config.go | 7 + params/config.go | 41 +++- params/version.go | 4 +- rollup/sync_service/bindings.go | 150 ++++++++++++++ rollup/sync_service/bridge_client.go | 129 ++++++++++++ rollup/sync_service/sync_service.go | 227 ++++++++++++++++++++++ rollup/sync_service/types.go | 19 ++ 50 files changed, 1988 insertions(+), 99 deletions(-) create mode 100644 core/rawdb/accessors_l1_message.go create mode 100644 core/rawdb/accessors_l1_message_test.go create mode 100644 core/types/l1_message_tx.go create mode 100644 rollup/sync_service/bindings.go create mode 100644 rollup/sync_service/bridge_client.go create mode 100644 rollup/sync_service/sync_service.go create mode 100644 rollup/sync_service/types.go diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 5456712d5..9f8e483fa 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -814,6 +814,7 @@ func (m callMsg) Gas() uint64 { return m.CallMsg.Gas } 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 } // filterBackend implements filters.Backend to support filtering for logs without // taking bloom-bits acceleration structures into account. diff --git a/cmd/devp2p/internal/ethtest/suite_test.go b/cmd/devp2p/internal/ethtest/suite_test.go index 42ebb3c69..2fc7aa30b 100644 --- a/cmd/devp2p/internal/ethtest/suite_test.go +++ b/cmd/devp2p/internal/ethtest/suite_test.go @@ -97,7 +97,7 @@ func setupGeth(stack *node.Node) error { TrieDirtyCache: 16, TrieTimeout: 60 * time.Minute, SnapshotCache: 10, - }) + }, nil) if err != nil { return err } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 3b121be80..d37e95ec3 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -158,6 +158,9 @@ var ( utils.MinerNotifyFullFlag, configFileFlag, utils.CatalystFlag, + utils.L1EndpointFlag, + utils.L1ConfirmationsFlag, + utils.L1DeploymentBlockFlag, } rpcFlags = []cli.Flag{ diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 161b71040..4c444436e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -19,6 +19,7 @@ package utils import ( "crypto/ecdsa" + "errors" "fmt" "io" "io/ioutil" @@ -41,6 +42,7 @@ import ( "github.com/scroll-tech/go-ethereum/accounts/keystore" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common/fdlimit" + "github.com/scroll-tech/go-ethereum/common/hexutil" "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/clique" "github.com/scroll-tech/go-ethereum/consensus/ethash" @@ -53,6 +55,7 @@ import ( "github.com/scroll-tech/go-ethereum/eth/ethconfig" "github.com/scroll-tech/go-ethereum/eth/gasprice" "github.com/scroll-tech/go-ethereum/eth/tracers" + "github.com/scroll-tech/go-ethereum/ethclient" "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/ethstats" "github.com/scroll-tech/go-ethereum/graphql" @@ -70,6 +73,7 @@ import ( "github.com/scroll-tech/go-ethereum/p2p/nat" "github.com/scroll-tech/go-ethereum/p2p/netutil" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rpc" ) func init() { @@ -794,6 +798,20 @@ var ( Name: "catalyst", Usage: "Catalyst mode (eth2 integration testing)", } + + // L1Settings + L1EndpointFlag = cli.StringFlag{ + Name: "l1.endpoint", + Usage: "Endpoint of L1 HTTP-RPC server", + } + L1ConfirmationsFlag = cli.StringFlag{ + Name: "l1.confirmations", + Usage: "Number of confirmations on L1 needed for finalization, or \"safe\" or \"finalized\"", + } + L1DeploymentBlockFlag = cli.Int64Flag{ + Name: "l1.sync.startblock", + Usage: "L1 block height to start syncing from. Should be set to the L1 message queue deployment block number.", + } ) // MakeDataDir retrieves the currently requested data directory, terminating @@ -1226,6 +1244,7 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { setNodeUserIdent(ctx, cfg) setDataDir(ctx, cfg) setSmartCard(ctx, cfg) + setL1(ctx, cfg) if ctx.GlobalIsSet(ExternalSignerFlag.Name) { cfg.ExternalSigner = ctx.GlobalString(ExternalSignerFlag.Name) @@ -1251,6 +1270,42 @@ func SetNodeConfig(ctx *cli.Context, cfg *node.Config) { } } +func unmarshalBlockNumber(input string) (rpc.BlockNumber, error) { + switch input { + case "finalized": + return rpc.FinalizedBlockNumber, nil + case "safe": + return rpc.SafeBlockNumber, nil + } + blockNum, err := hexutil.DecodeUint64(input) + if err == nil && blockNum <= math.MaxInt64 { + return rpc.BlockNumber(blockNum), nil + } + blockNum, err = strconv.ParseUint(input, 10, 64) + if err == nil && blockNum <= math.MaxInt64 { + return rpc.BlockNumber(blockNum), nil + } + return 0, errors.New("incorrect value") +} + +func setL1(ctx *cli.Context, cfg *node.Config) { + var err error + if ctx.GlobalIsSet(L1EndpointFlag.Name) { + cfg.L1Endpoint = ctx.GlobalString(L1EndpointFlag.Name) + } + if ctx.GlobalIsSet(L1ConfirmationsFlag.Name) { + cfg.L1Confirmations, err = unmarshalBlockNumber(ctx.GlobalString(L1ConfirmationsFlag.Name)) + if err != nil { + panic(fmt.Sprintf("invalid value for flag %s: %s", L1ConfirmationsFlag.Name, ctx.GlobalString(L1ConfirmationsFlag.Name))) + } + } else { + cfg.L1Confirmations = rpc.FinalizedBlockNumber + } + if ctx.GlobalIsSet(L1DeploymentBlockFlag.Name) { + cfg.L1DeploymentBlock = ctx.GlobalUint64(L1DeploymentBlockFlag.Name) + } +} + func setSmartCard(ctx *cli.Context, cfg *node.Config) { // Skip enabling smartcards if no path is set path := ctx.GlobalString(SmartCardDaemonPathFlag.Name) @@ -1732,7 +1787,23 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (ethapi.Backend stack.RegisterAPIs(tracers.APIs(backend.ApiBackend)) return backend.ApiBackend, nil } - backend, err := eth.New(stack, cfg) + + // initialize L1 client for sync service + // note: we need to do this here to avoid circular dependency + l1EndpointUrl := stack.Config().L1Endpoint + var l1Client *ethclient.Client + + if l1EndpointUrl != "" { + var err error + l1Client, err = ethclient.Dial(l1EndpointUrl) + if err != nil { + Fatalf("Unable to connect to L1 endpoint at %v: %v", l1EndpointUrl, err) + } + + log.Info("Initialized L1 client", "endpoint", l1EndpointUrl) + } + + backend, err := eth.New(stack, cfg, l1Client) if err != nil { Fatalf("Failed to register the Ethereum service: %v", err) } diff --git a/consensus/errors.go b/consensus/errors.go index a3c8f737f..978d0974c 100644 --- a/consensus/errors.go +++ b/consensus/errors.go @@ -37,4 +37,19 @@ var ( // ErrInvalidTxCount is returned if a block contains too many transactions. ErrInvalidTxCount = errors.New("invalid transaction count") + + // ErrMissingL1MessageData is returned if a block contains L1 messages that the + // node has not synced yet. In this case we insert the block into the future + // queue and process it again later. + ErrMissingL1MessageData = errors.New("unknown L1 message data") + + // ErrInvalidL1MessageOrder is returned if a block contains L1 messages in the wrong + // order. Possible scenarios are: (1) L1 messages do not follow their QueueIndex order, + // (2) the block skipped one or more L1 messages, (3) L1 messages are not included in + // a contiguous block at the front of the block. + ErrInvalidL1MessageOrder = errors.New("invalid L1 message order") + + // ErrUnknownL1Message is returned if a block contains an L1 message that does not + // match the corresponding message in the node's local database. + ErrUnknownL1Message = errors.New("unknown L1 message") ) diff --git a/console/console_test.go b/console/console_test.go index 772bf877e..85f93fda8 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -110,7 +110,7 @@ func newTester(t *testing.T, confOverride func(*ethconfig.Config)) *tester { if confOverride != nil { confOverride(ethConf) } - ethBackend, err := eth.New(stack, ethConf) + ethBackend, err := eth.New(stack, ethConf, nil) if err != nil { t.Fatalf("failed to register Ethereum protocol: %v", err) } diff --git a/core/block_validator.go b/core/block_validator.go index 4a0a59d75..06b197d67 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/scroll-tech/go-ethereum/consensus" + "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/params" @@ -54,7 +55,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { return ErrKnownBlock } - if !v.config.Scroll.IsValidTxCount(len(block.Transactions())) { + if !v.config.Scroll.IsValidL2TxCount(block.CountL2Tx()) { return consensus.ErrInvalidTxCount } // Check if block payload size is smaller than the max size @@ -78,6 +79,73 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } return consensus.ErrPrunedAncestor } + return v.ValidateL1Messages(block) +} + +// 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. +// - The first L1 message's QueueIndex is right after the last L1 message included in the chain. +// - L1 messages follow the QueueIndex order. No L1 message is skipped. +// - The L1 messages included in the block match the node's view of the L1 ledger. +func (v *BlockValidator) ValidateL1Messages(block *types.Block) error { + // no further processing if the block contains no L1 messages + if block.L1MessageCount() == 0 { + return nil + } + + if v.config.Scroll.L1Config == nil { + // TODO: should we allow follower nodes to skip L1 message verification? + panic("Running on L1Message-enabled network but no l1Config was provided") + } + + nextQueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(v.bc.db, block.ParentHash()) + if nextQueueIndex == nil { + // we'll reprocess this block at a later time + return consensus.ErrMissingL1MessageData + } + queueIndex := *nextQueueIndex + + L1SectionOver := false + it := rawdb.IterateL1MessagesFrom(v.bc.db, queueIndex) + + for _, tx := range block.Transactions() { + if !tx.IsL1MessageTx() { + L1SectionOver = true + continue // we do not verify L2 transactions here + } + + // check that L1 messages are before L2 transactions + if L1SectionOver { + return consensus.ErrInvalidL1MessageOrder + } + + // check queue index + // TODO: account for skipped messages here + if tx.AsL1MessageTx().QueueIndex != queueIndex { + return consensus.ErrInvalidL1MessageOrder + } + + queueIndex += 1 + + if exists := it.Next(); !exists { + // we'll reprocess this block at a later time + return consensus.ErrMissingL1MessageData + } + + // check that the L1 message in the block is the same that we collected from L1 + msg := it.L1Message() + expectedHash := types.NewTx(&msg).Hash() + + if tx.Hash() != expectedHash { + return consensus.ErrUnknownL1Message + } + } + + // TODO: consider adding a rule to enforce L1Config.NumL1MessagesPerBlock. + // If there are L1 messages available, sequencer nodes should include them. + // However, this is hard to enforce as different nodes might have different views of L1. + return nil } diff --git a/core/blockchain.go b/core/blockchain.go index 7fc8711ee..a3bb38ab4 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -277,6 +277,9 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, chainConfig *par return nil, ErrNoGenesis } + // initialize L1 message index for genesis block + rawdb.WriteFirstQueueIndexNotInL2Block(db, bc.genesisBlock.Hash(), 0) + var nilBlock *types.Block bc.currentBlock.Store(nilBlock) bc.currentFastBlock.Store(nilBlock) @@ -695,6 +698,7 @@ func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error { batch := bc.db.NewBatch() rawdb.WriteTd(batch, genesis.Hash(), genesis.NumberU64(), genesis.Difficulty()) rawdb.WriteBlock(batch, genesis) + rawdb.WriteFirstQueueIndexNotInL2Block(batch, genesis.Hash(), 0) if err := batch.Write(); err != nil { log.Crit("Failed to write genesis block", "err", err) } @@ -1176,6 +1180,14 @@ func (bc *BlockChain) writeBlockWithoutState(block *types.Block, td *big.Int) (e batch := bc.db.NewBatch() rawdb.WriteTd(batch, block.Hash(), block.NumberU64(), td) rawdb.WriteBlock(batch, block) + + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.ParentHash()) + if queueIndex != nil { + // note: we can insert blocks with header-only ancestors here, + // so queueIndex might not yet be available in DB. + rawdb.WriteFirstQueueIndexNotInL2Block(batch, block.Hash(), *queueIndex+uint64(block.L1MessageCount())) + } + if err := batch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } @@ -1230,6 +1242,15 @@ func (bc *BlockChain) writeBlockWithState(block *types.Block, receipts []*types. rawdb.WriteBlock(blockBatch, block) rawdb.WriteReceipts(blockBatch, block.Hash(), block.NumberU64(), receipts) rawdb.WritePreimages(blockBatch, state.Preimages()) + + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(bc.db, block.ParentHash()) + if queueIndex == nil { + // We expect that we only insert contiguous chain segments, + // so the parent will always be inserted first. + log.Crit("Queue index in DB is nil", "parent", block.ParentHash(), "hash", block.Hash()) + } + rawdb.WriteFirstQueueIndexNotInL2Block(blockBatch, block.Hash(), *queueIndex+uint64(block.L1MessageCount())) + if err := blockBatch.Write(); err != nil { log.Crit("Failed to write block into disk", "err", err) } @@ -1500,7 +1521,7 @@ func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, er return bc.insertSideChain(block, it) // First block is future, shove it (and all children) to the future queue (unknown ancestor) - case errors.Is(err, consensus.ErrFutureBlock) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())): + case errors.Is(err, consensus.ErrFutureBlock) || errors.Is(err, consensus.ErrMissingL1MessageData) || (errors.Is(err, consensus.ErrUnknownAncestor) && bc.futureBlocks.Contains(it.first().ParentHash())): for block != nil && (it.index == 0 || errors.Is(err, consensus.ErrUnknownAncestor)) { log.Debug("Future block, postponing import", "number", block.Number(), "hash", block.Hash()) if err := bc.addFutureBlock(block); err != nil { @@ -2175,11 +2196,12 @@ Chain config: %v Number: %v Hash: 0x%x +ParentHash: 0x%x %v Error: %v ############################## -`, bc.chainConfig, block.Number(), block.Hash(), receiptString, err)) +`, bc.chainConfig, block.Number(), block.Hash(), block.ParentHash(), receiptString, err)) } // InsertHeaderChain attempts to insert the given header chain in to the local diff --git a/core/blockchain_test.go b/core/blockchain_test.go index ab149d15a..2bd033ca7 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -3239,6 +3239,168 @@ func TestTransactionCountLimit(t *testing.T) { } } +// TestInsertBlocksWithL1Messages tests that the chain accepts blocks with L1MessageTx transactions. +func TestInsertBlocksWithL1Messages(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + engine = ethash.NewFaker() + ) + + // initialize genesis + config := params.AllEthashProtocolChanges + config.Scroll.L1Config.NumL1MessagesPerBlock = 1 + + genspec := &Genesis{ + Config: config, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genesis := genspec.MustCommit(db) + + // initialize L1 message DB + msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 2, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 3, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + } + rawdb.WriteL1Messages(db, msgs) + + // initialize blockchain + blockchain, _ := NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil) + defer blockchain.Stop() + + // generate blocks with 1 L1 message in each + blocks, _ := GenerateChain(config, genesis, engine, db, len(msgs), func(i int, b *BlockGen) { + tx := types.NewTx(&msgs[i]) + b.AddTxWithChain(blockchain, tx) + }) + + // insert blocks, validation should pass + index, err := blockchain.InsertChain(blocks) + assert.Nil(t, err) + assert.Equal(t, len(msgs), index) + + // L1 message DB should be updated + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, blocks[len(blocks)-1].Hash()) + assert.NotNil(t, queueIndex) + assert.Equal(t, uint64(len(msgs)), *queueIndex) + + // generate fork with 2 L1 messages in each block + blocks, _ = GenerateChain(config, genesis, engine, db, len(msgs)/2, func(i int, b *BlockGen) { + tx1 := types.NewTx(&msgs[2*i]) + b.AddTxWithChain(blockchain, tx1) + tx2 := types.NewTx(&msgs[2*i+1]) + b.AddTxWithChain(blockchain, tx2) + }) + + // insert blocks, validation should pass + index, err = blockchain.InsertChain(blocks) + assert.Nil(t, err) + assert.Equal(t, len(msgs)/2, index) + + // L1 message DB should be updated + queueIndex = rawdb.ReadFirstQueueIndexNotInL2Block(db, blocks[len(blocks)-1].Hash()) + assert.NotNil(t, queueIndex) + assert.Equal(t, uint64(len(msgs)), *queueIndex) +} + +// TestL1MessageValidationFailure tests that the chain rejects blocks with incorrect L1MessageTx transactions. +func TestL1MessageValidationFailure(t *testing.T) { + var ( + db = rawdb.NewMemoryDatabase() + engine = ethash.NewFaker() + key, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr = crypto.PubkeyToAddress(key.PublicKey) + signer = new(types.HomesteadSigner) + ) + + // initialize genesis + config := params.AllEthashProtocolChanges + config.Scroll.L1Config.NumL1MessagesPerBlock = 1 + + genspec := &Genesis{ + Config: config, + Alloc: map[common.Address]GenesisAccount{ + addr: {Balance: big.NewInt(10000000000000000)}, + }, + BaseFee: big.NewInt(params.InitialBaseFee), + } + genesis := genspec.MustCommit(db) + + // initialize L1 message DB + msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 2, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + } + rawdb.WriteL1Messages(db, msgs) + + // initialize blockchain + blockchain, _ := NewBlockChain(db, nil, config, engine, vm.Config{}, nil, nil) + defer blockchain.Stop() + + generateBlock := func(txs []*types.Transaction) ([]*types.Block, []types.Receipts) { + return GenerateChain(config, genesis, engine, db, 1, func(i int, b *BlockGen) { + for _, tx := range txs { + b.AddTxWithChain(blockchain, tx) + } + }) + } + + // skip #0 + blocks, _ := generateBlock([]*types.Transaction{types.NewTx(&msgs[1])}) + index, err := blockchain.InsertChain(blocks) + assert.Equal(t, 0, index) + assert.Equal(t, consensus.ErrInvalidL1MessageOrder, err) + assert.Equal(t, big.NewInt(0), blockchain.CurrentBlock().Number()) + + // skip #1 + blocks, _ = generateBlock([]*types.Transaction{types.NewTx(&msgs[0]), types.NewTx(&msgs[2])}) + index, err = blockchain.InsertChain(blocks) + assert.Equal(t, 0, index) + assert.Equal(t, consensus.ErrInvalidL1MessageOrder, err) + assert.Equal(t, big.NewInt(0), blockchain.CurrentBlock().Number()) + + // L2 tx precedes L1 message tx + tx, _ := types.SignTx(types.NewTransaction(0, common.Address{0x00}, new(big.Int), params.TxGas, genspec.BaseFee, nil), signer, key) + blocks, _ = generateBlock([]*types.Transaction{tx, types.NewTx(&msgs[0])}) + index, err = blockchain.InsertChain(blocks) + assert.Equal(t, 0, index) + assert.Equal(t, consensus.ErrInvalidL1MessageOrder, err) + assert.Equal(t, big.NewInt(0), blockchain.CurrentBlock().Number()) + + // unknown message + unknown := types.L1MessageTx{QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x02}, Sender: common.Address{2}} + blocks, _ = generateBlock([]*types.Transaction{types.NewTx(&msgs[0]), types.NewTx(&unknown)}) + index, err = blockchain.InsertChain(blocks) + assert.Equal(t, 0, index) + assert.Equal(t, consensus.ErrUnknownL1Message, err) + assert.Equal(t, big.NewInt(0), blockchain.CurrentBlock().Number()) + + // missing message + msg := types.L1MessageTx{QueueIndex: 3, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}} + blocks, _ = generateBlock([]*types.Transaction{types.NewTx(&msgs[0]), types.NewTx(&msgs[1]), types.NewTx(&msgs[2]), types.NewTx(&msg)}) + index, err = blockchain.InsertChain(blocks) + assert.Equal(t, 1, index) + assert.NoError(t, err) + + // blocks is inserted into future blocks queue + assert.Equal(t, big.NewInt(0), blockchain.CurrentBlock().Number()) + + // insert missing message into DB + rawdb.WriteL1Message(db, msg) + blockchain.procFutureBlocks() + + // the block is now processed + assert.Equal(t, big.NewInt(1), blockchain.CurrentBlock().Number()) + assert.Equal(t, blocks[0].Hash(), blockchain.CurrentBlock().Hash()) + + // L1 message DB should be updated + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, blocks[0].Hash()) + assert.NotNil(t, queueIndex) + assert.Equal(t, uint64(4), *queueIndex) +} + func TestBlockPayloadSizeLimit(t *testing.T) { // Create config that allows at most 150 bytes per block payload config := params.TestChainConfig diff --git a/core/events.go b/core/events.go index 398bac1ba..a2f86c52e 100644 --- a/core/events.go +++ b/core/events.go @@ -41,3 +41,6 @@ type ChainSideEvent struct { } type ChainHeadEvent struct{ Block *types.Block } + +// NewL1MsgsEvent is posted when we receive some new messages from L1. +type NewL1MsgsEvent struct{ Count int } diff --git a/core/genesis.go b/core/genesis.go index bcdb84654..eff72d83a 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -352,6 +352,7 @@ func (g *Genesis) Commit(db ethdb.Database) (*types.Block, error) { rawdb.WriteHeadFastBlockHash(db, block.Hash()) rawdb.WriteHeadHeaderHash(db, block.Hash()) rawdb.WriteChainConfig(db, block.Hash(), config) + rawdb.WriteFirstQueueIndexNotInL2Block(db, block.Hash(), 0) return block, nil } diff --git a/core/rawdb/accessors_l1_message.go b/core/rawdb/accessors_l1_message.go new file mode 100644 index 000000000..61226ceb3 --- /dev/null +++ b/core/rawdb/accessors_l1_message.go @@ -0,0 +1,211 @@ +package rawdb + +import ( + "bytes" + "encoding/binary" + "errors" + "math/big" + + leveldb "github.com/syndtr/goleveldb/leveldb/errors" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/ethdb" + "github.com/scroll-tech/go-ethereum/ethdb/memorydb" + "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rlp" +) + +func isNotFoundErr(err error) bool { + return errors.Is(err, leveldb.ErrNotFound) || errors.Is(err, memorydb.ErrMemorydbNotFound) +} + +// WriteSyncedL1BlockNumber writes the highest synced L1 block number to the database. +func WriteSyncedL1BlockNumber(db ethdb.KeyValueWriter, L1BlockNumber uint64) { + value := big.NewInt(0).SetUint64(L1BlockNumber).Bytes() + + if err := db.Put(syncedL1BlockNumberKey, value); err != nil { + log.Crit("Failed to update synced L1 block number", "err", err) + } +} + +// ReadSyncedL1BlockNumber retrieves the highest synced L1 block number. +func ReadSyncedL1BlockNumber(db ethdb.Reader) *uint64 { + data, err := db.Get(syncedL1BlockNumberKey) + if err != nil && isNotFoundErr(err) { + return nil + } + if err != nil { + log.Crit("Failed to read synced L1 block number from database", "err", err) + } + if len(data) == 0 { + return nil + } + + number := new(big.Int).SetBytes(data) + if !number.IsUint64() { + log.Crit("Unexpected synced L1 block number in database", "number", number) + } + + value := number.Uint64() + return &value +} + +// WriteL1Message writes an L1 message to the database. +func WriteL1Message(db ethdb.KeyValueWriter, l1Msg types.L1MessageTx) { + bytes, err := rlp.EncodeToBytes(l1Msg) + if err != nil { + log.Crit("Failed to RLP encode L1 message", "err", err) + } + if err := db.Put(L1MessageKey(l1Msg.QueueIndex), bytes); err != nil { + log.Crit("Failed to store L1 message", "err", err) + } +} + +// WriteL1Messages writes an array of L1 messages to the database. +// Note: pass a db of type `ethdb.Batcher` to batch writes in memory. +func WriteL1Messages(db ethdb.KeyValueWriter, l1Msgs []types.L1MessageTx) { + for _, msg := range l1Msgs { + WriteL1Message(db, msg) + } +} + +// ReadL1MessageRLP retrieves an L1 message in its raw RLP database encoding. +func ReadL1MessageRLP(db ethdb.Reader, queueIndex uint64) rlp.RawValue { + data, err := db.Get(L1MessageKey(queueIndex)) + if err != nil && isNotFoundErr(err) { + return nil + } + if err != nil { + log.Crit("Failed to load L1 message", "queueIndex", queueIndex, "err", err) + } + return data +} + +// ReadL1Message retrieves the L1 message corresponding to the enqueue index. +func ReadL1Message(db ethdb.Reader, queueIndex uint64) *types.L1MessageTx { + data := ReadL1MessageRLP(db, queueIndex) + if len(data) == 0 { + return nil + } + l1Msg := new(types.L1MessageTx) + if err := rlp.Decode(bytes.NewReader(data), l1Msg); err != nil { + log.Crit("Invalid L1 message RLP", "queueIndex", queueIndex, "data", data, "err", err) + } + return l1Msg +} + +// L1MessageIterator is a wrapper around ethdb.Iterator that +// allows us to iterate over L1 messages in the database. It +// implements an interface similar to ethdb.Iterator. +type L1MessageIterator struct { + inner ethdb.Iterator + keyLength int +} + +// IterateL1MessagesFrom creates an L1MessageIterator that iterates over +// all L1 message in the database starting at the provided enqueue index. +func IterateL1MessagesFrom(db ethdb.Iteratee, fromQueueIndex uint64) L1MessageIterator { + start := encodeQueueIndex(fromQueueIndex) + it := db.NewIterator(l1MessagePrefix, start) + keyLength := len(l1MessagePrefix) + 8 + + return L1MessageIterator{ + inner: it, + keyLength: keyLength, + } +} + +// Next moves the iterator to the next key/value pair. +// It returns false when the iterator is exhausted. +// TODO: Consider reading items in batches. +func (it *L1MessageIterator) Next() bool { + for it.inner.Next() { + key := it.inner.Key() + if len(key) == it.keyLength { + return true + } + } + return false +} + +// QueueIndex returns the enqueue index of the current L1 message. +func (it *L1MessageIterator) QueueIndex() uint64 { + key := it.inner.Key() + raw := key[len(l1MessagePrefix) : len(l1MessagePrefix)+8] + queueIndex := binary.BigEndian.Uint64(raw) + return queueIndex +} + +// L1Message returns the current L1 message. +func (it *L1MessageIterator) L1Message() types.L1MessageTx { + data := it.inner.Value() + l1Msg := types.L1MessageTx{} + if err := rlp.DecodeBytes(data, &l1Msg); err != nil { + log.Crit("Invalid L1 message RLP", "data", data, "err", err) + } + return l1Msg +} + +// Release releases the associated resources. +func (it *L1MessageIterator) Release() { + it.inner.Release() +} + +// ReadL1MessagesFrom retrieves up to `maxCount` L1 messages starting at `startIndex`. +func ReadL1MessagesFrom(db ethdb.Iteratee, startIndex, maxCount uint64) []types.L1MessageTx { + msgs := make([]types.L1MessageTx, 0, maxCount) + it := IterateL1MessagesFrom(db, startIndex) + defer it.Release() + + index := startIndex + count := maxCount + + for count > 0 && it.Next() { + msg := it.L1Message() + + // sanity check + if msg.QueueIndex != index { + log.Crit( + "Unexpected QueueIndex in ReadL1MessagesFrom", + "expected", index, + "got", msg.QueueIndex, + "startIndex", startIndex, + "maxCount", maxCount, + ) + } + + msgs = append(msgs, msg) + index += 1 + count -= 1 + } + + return msgs +} + +// WriteFirstQueueIndexNotInL2Block writes the queue index of the first message +// that is NOT included in the ledger up to and including the provided L2 block. +// The L2 block is identified by its block hash. If the L2 block contains zero +// L1 messages, this value MUST equal its parent's value. +func WriteFirstQueueIndexNotInL2Block(db ethdb.KeyValueWriter, l2BlockHash common.Hash, queueIndex uint64) { + if err := db.Put(FirstQueueIndexNotInL2BlockKey(l2BlockHash), encodeQueueIndex(queueIndex)); err != nil { + log.Crit("Failed to store first L1 message not in L2 block", "l2BlockHash", l2BlockHash, "err", err) + } +} + +// ReadFirstQueueIndexNotInL2Block retrieves the queue index of the first message +// that is NOT included in the ledger up to and including the provided L2 block. +func ReadFirstQueueIndexNotInL2Block(db ethdb.Reader, l2BlockHash common.Hash) *uint64 { + data, err := db.Get(FirstQueueIndexNotInL2BlockKey(l2BlockHash)) + if err != nil && isNotFoundErr(err) { + return nil + } + if err != nil { + log.Crit("Failed to read first L1 message not in L2 block from database", "l2BlockHash", l2BlockHash, "err", err) + } + if len(data) == 0 { + return nil + } + queueIndex := binary.BigEndian.Uint64(data) + return &queueIndex +} diff --git a/core/rawdb/accessors_l1_message_test.go b/core/rawdb/accessors_l1_message_test.go new file mode 100644 index 000000000..fe203d755 --- /dev/null +++ b/core/rawdb/accessors_l1_message_test.go @@ -0,0 +1,127 @@ +package rawdb + +import ( + "math/big" + "testing" + + "github.com/scroll-tech/go-ethereum/common" + "github.com/scroll-tech/go-ethereum/core/types" +) + +func TestReadWriteSyncedL1BlockNumber(t *testing.T) { + blockNumbers := []uint64{ + 1, + 1 << 2, + 1 << 8, + 1 << 16, + 1 << 32, + } + + db := NewMemoryDatabase() + for _, num := range blockNumbers { + WriteSyncedL1BlockNumber(db, num) + got := ReadSyncedL1BlockNumber(db) + + if got == nil || *got != num { + t.Fatal("Block number mismatch", "expected", num, "got", got) + } + } +} + +func newL1MessageTx(queueIndex uint64) types.L1MessageTx { + return types.L1MessageTx{ + QueueIndex: queueIndex, + Gas: 0, + To: &common.Address{}, + Value: big.NewInt(0), + Data: nil, + Sender: common.Address{}, + } +} + +func TestReadWriteL1Message(t *testing.T) { + queueIndex := uint64(123) + msg := newL1MessageTx(queueIndex) + db := NewMemoryDatabase() + WriteL1Messages(db, []types.L1MessageTx{msg}) + got := ReadL1Message(db, queueIndex) + if got == nil || got.QueueIndex != queueIndex { + t.Fatal("L1 message mismatch", "expected", queueIndex, "got", got) + } +} + +func TestIterateL1Message(t *testing.T) { + msgs := []types.L1MessageTx{ + newL1MessageTx(100), + newL1MessageTx(101), + newL1MessageTx(103), + newL1MessageTx(200), + newL1MessageTx(1000), + } + + db := NewMemoryDatabase() + WriteL1Messages(db, msgs) + + it := IterateL1MessagesFrom(db, 103) + defer it.Release() + + for ii := 2; ii < len(msgs); ii++ { + finished := !it.Next() + if finished { + t.Fatal("Iterator terminated early", "ii", ii) + } + + got := it.L1Message() + if got.QueueIndex != msgs[ii].QueueIndex { + t.Fatal("Invalid result", "expected", msgs[ii].QueueIndex, "got", got.QueueIndex) + } + } + + finished := !it.Next() + if !finished { + t.Fatal("Iterator did not terminate") + } +} + +func TestReadL1MessageTxRange(t *testing.T) { + msgs := []types.L1MessageTx{ + newL1MessageTx(100), + newL1MessageTx(101), + newL1MessageTx(102), + newL1MessageTx(103), + } + + db := NewMemoryDatabase() + WriteL1Messages(db, msgs) + + got := ReadL1MessagesFrom(db, 101, 3) + + if len(got) != 3 { + t.Fatal("Invalid length", "expected", 3, "got", len(got)) + } + + if got[0].QueueIndex != 101 || got[1].QueueIndex != 102 || got[2].QueueIndex != 103 { + t.Fatal("Invalid result", "got", got) + } +} + +func TestReadWriteLastL1MessageInL2Block(t *testing.T) { + inputs := []uint64{ + 1, + 1 << 2, + 1 << 8, + 1 << 16, + 1 << 32, + } + + db := NewMemoryDatabase() + for _, num := range inputs { + l2BlockHash := common.Hash{byte(num)} + WriteFirstQueueIndexNotInL2Block(db, l2BlockHash, num) + got := ReadFirstQueueIndexNotInL2Block(db, l2BlockHash) + + if got == nil || *got != num { + t.Fatal("Enqueue index mismatch", "expected", num, "got", got) + } + } +} diff --git a/core/rawdb/database.go b/core/rawdb/database.go index 0b43504f0..219f0f666 100644 --- a/core/rawdb/database.go +++ b/core/rawdb/database.go @@ -323,6 +323,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { preimages stat bloomBits stat cliqueSnaps stat + l1Messages stat + lastL1Message stat // Ancient store statistics ancientHeadersSize common.StorageSize @@ -382,6 +384,10 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { bloomBits.Add(size) case bytes.HasPrefix(key, []byte("clique-")) && len(key) == 7+common.HashLength: cliqueSnaps.Add(size) + case bytes.HasPrefix(key, l1MessagePrefix) && len(key) == len(l1MessagePrefix)+8: + l1Messages.Add(size) + case bytes.HasPrefix(key, firstQueueIndexNotInL2BlockPrefix) && len(key) == len(firstQueueIndexNotInL2BlockPrefix)+common.HashLength: + lastL1Message.Add(size) case bytes.HasPrefix(key, []byte("cht-")) || bytes.HasPrefix(key, []byte("chtIndexV2-")) || bytes.HasPrefix(key, []byte("chtRootV2-")): // Canonical hash trie @@ -396,7 +402,7 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { databaseVersionKey, headHeaderKey, headBlockKey, headFastBlockKey, lastPivotKey, fastTrieProgressKey, snapshotDisabledKey, SnapshotRootKey, snapshotJournalKey, snapshotGeneratorKey, snapshotRecoveryKey, txIndexTailKey, fastTxLookupLimitKey, - uncleanShutdownKey, badBlockKey, + uncleanShutdownKey, badBlockKey, syncedL1BlockNumberKey, } { if bytes.Equal(key, meta) { metadata.Add(size) @@ -444,6 +450,8 @@ func InspectDatabase(db ethdb.Database, keyPrefix, keyStart []byte) error { {"Key-Value store", "Storage snapshot", storageSnaps.Size(), storageSnaps.Count()}, {"Key-Value store", "Clique snapshots", cliqueSnaps.Size(), cliqueSnaps.Count()}, {"Key-Value store", "Singleton metadata", metadata.Size(), metadata.Count()}, + {"Key-Value store", "L1 messages", l1Messages.Size(), l1Messages.Count()}, + {"Key-Value store", "Last L1 message", lastL1Message.Size(), lastL1Message.Count()}, {"Ancient store", "Headers", ancientHeadersSize.String(), ancients.String()}, {"Ancient store", "Bodies", ancientBodiesSize.String(), ancients.String()}, {"Ancient store", "Receipt lists", ancientReceiptsSize.String(), ancients.String()}, diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 73dc69ea3..de264b665 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -98,6 +98,11 @@ var ( preimageCounter = metrics.NewRegisteredCounter("db/preimage/total", nil) preimageHitCounter = metrics.NewRegisteredCounter("db/preimage/hits", nil) + + // Scroll L1 message store + syncedL1BlockNumberKey = []byte("LastSyncedL1BlockNumber") + l1MessagePrefix = []byte("l1") // l1MessagePrefix + queueIndex (uint64 big endian) -> L1MessageTx + firstQueueIndexNotInL2BlockPrefix = []byte("q") // firstQueueIndexNotInL2BlockPrefix + L2 block hash -> enqueue index ) const ( @@ -230,3 +235,20 @@ func IsCodeKey(key []byte) (bool, []byte) { func configKey(hash common.Hash) []byte { return append(configPrefix, hash.Bytes()...) } + +// encodeQueueIndex encodes an L1 enqueue index as big endian uint64 +func encodeQueueIndex(index uint64) []byte { + enc := make([]byte, 8) + binary.BigEndian.PutUint64(enc, index) + return enc +} + +// L1MessageKey = l1MessagePrefix + queueIndex (uint64 big endian) +func L1MessageKey(queueIndex uint64) []byte { + return append(l1MessagePrefix, encodeQueueIndex(queueIndex)...) +} + +// FirstQueueIndexNotInL2BlockKey = firstQueueIndexNotInL2BlockPrefix + L2 block hash +func FirstQueueIndexNotInL2BlockKey(l2BlockHash common.Hash) []byte { + return append(firstQueueIndexNotInL2BlockPrefix, l2BlockHash.Bytes()...) +} diff --git a/core/state_transition.go b/core/state_transition.go index 7df7c74d1..6b13d6541 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -82,6 +82,7 @@ type Message interface { IsFake() bool Data() []byte AccessList() types.AccessList + IsL1MessageTx() bool } // ExecutionResult includes all output after executing given evm @@ -253,6 +254,14 @@ func (st *StateTransition) buyGas() error { } func (st *StateTransition) preCheck() error { + 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! + st.gas += st.msg.Gas() + st.initialGas = st.msg.Gas() + return st.gp.SubGas(st.msg.Gas()) // gas used by deposits may not be used by other txs + } + // Only check transactions that are not fake if !st.msg.IsFake() { // Make sure this transaction's nonce is correct. @@ -379,6 +388,16 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value) } + // no refunds for l1 messages + if st.msg.IsL1MessageTx() { + return &ExecutionResult{ + L1Fee: big.NewInt(0), + UsedGas: st.gasUsed(), + Err: vmerr, + ReturnData: ret, + }, nil + } + if !london { // Before EIP-3529: refunds were capped to gasUsed / 2 st.refundGas(params.RefundQuotient) diff --git a/core/tx_pool.go b/core/tx_pool.go index 5fa2b5c47..810cb8edd 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -603,6 +603,11 @@ 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() { + return ErrTxTypeNotSupported + } + // Accept only legacy transactions until EIP-2718/2930 activates. if !pool.eip2718 && tx.Type() != types.LegacyTxType { return ErrTxTypeNotSupported diff --git a/core/types/block.go b/core/types/block.go index b8e44344f..f3e7c8379 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -166,8 +166,9 @@ type Block struct { transactions Transactions // caches - hash atomic.Value - size atomic.Value + hash atomic.Value + size atomic.Value + l1MsgCount atomic.Value // Td is used by package core to store the total difficulty // of the chain up to and including the block. @@ -396,4 +397,24 @@ func (b *Block) Hash() common.Hash { return v } +// L1MessageCount returns the number of L1 messages in this block. +func (b *Block) L1MessageCount() int { + if l1MsgCount := b.l1MsgCount.Load(); l1MsgCount != nil { + return l1MsgCount.(int) + } + count := 0 + for _, tx := range b.transactions { + if tx.IsL1MessageTx() { + count += 1 + } + } + b.l1MsgCount.Store(count) + return count +} + +// CountL2Tx returns the number of L2 transactions in this block. +func (b *Block) CountL2Tx() int { + return len(b.transactions) - b.L1MessageCount() +} + type Blocks []*Block diff --git a/core/types/l1_message_tx.go b/core/types/l1_message_tx.go new file mode 100644 index 000000000..5c5734b43 --- /dev/null +++ b/core/types/l1_message_tx.go @@ -0,0 +1,54 @@ +package types + +import ( + "math/big" + + "github.com/scroll-tech/go-ethereum/common" +) + +// payload, RLP encoded +type L1MessageTx struct { + QueueIndex uint64 + Gas uint64 // gas limit + To *common.Address // can not be nil, we do not allow contract creation from L1 + Value *big.Int + Data []byte + Sender common.Address +} + +// copy creates a deep copy of the transaction data and initializes all fields. +func (tx *L1MessageTx) copy() TxData { + cpy := &L1MessageTx{ + QueueIndex: tx.QueueIndex, + Gas: tx.Gas, + To: copyAddressPtr(tx.To), + Value: new(big.Int), + Data: common.CopyBytes(tx.Data), + Sender: tx.Sender, + } + if tx.Value != nil { + cpy.Value.Set(tx.Value) + } + return cpy +} + +// accessors for innerTx. +func (tx *L1MessageTx) txType() byte { return L1MessageTxType } +func (tx *L1MessageTx) chainID() *big.Int { return common.Big0 } +func (tx *L1MessageTx) accessList() AccessList { return nil } +func (tx *L1MessageTx) data() []byte { return tx.Data } +func (tx *L1MessageTx) gas() uint64 { return tx.Gas } +func (tx *L1MessageTx) gasFeeCap() *big.Int { return new(big.Int) } +func (tx *L1MessageTx) gasTipCap() *big.Int { return new(big.Int) } +func (tx *L1MessageTx) gasPrice() *big.Int { return new(big.Int) } +func (tx *L1MessageTx) value() *big.Int { return tx.Value } +func (tx *L1MessageTx) nonce() uint64 { return 0 } +func (tx *L1MessageTx) to() *common.Address { return tx.To } + +func (tx *L1MessageTx) rawSignatureValues() (v, r, s *big.Int) { + return common.Big0, common.Big0, common.Big0 +} + +func (tx *L1MessageTx) setSignatureValues(chainID, v, r, s *big.Int) { + // this is a noop for l1 message transactions +} diff --git a/core/types/l2trace.go b/core/types/l2trace.go index ef8fcdf55..0235d47e2 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -166,10 +166,16 @@ func NewTransactionData(tx *Transaction, blockNumber uint64, config *params.Chai signer := MakeSigner(config, big.NewInt(0).SetUint64(blockNumber)) from, _ := Sender(signer, tx) v, r, s := tx.RawSignatureValues() + + nonce := tx.Nonce() + if tx.IsL1MessageTx() { + nonce = tx.L1MessageQueueIndex() + } + result := &TransactionData{ Type: tx.Type(), TxHash: tx.Hash().String(), - Nonce: tx.Nonce(), + Nonce: nonce, ChainId: (*hexutil.Big)(tx.ChainId()), From: from, Gas: tx.Gas(), diff --git a/core/types/receipt.go b/core/types/receipt.go index aec4f43b9..f7312c473 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -435,6 +435,9 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { case DynamicFeeTxType: w.WriteByte(DynamicFeeTxType) rlp.Encode(w, data) + case L1MessageTxType: + w.WriteByte(L1MessageTxType) + 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/transaction.go b/core/types/transaction.go index 40e80685c..84a960eb6 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -45,6 +45,8 @@ const ( LegacyTxType = iota AccessListTxType DynamicFeeTxType + + L1MessageTxType = 0x7E ) // Transaction is an Ethereum transaction. @@ -186,6 +188,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) { var inner DynamicFeeTx err := rlp.DecodeBytes(b[1:], &inner) return &inner, err + case L1MessageTxType: + var inner L1MessageTx + err := rlp.DecodeBytes(b[1:], &inner) + return &inner, err default: return nil, ErrTxTypeNotSupported } @@ -287,6 +293,28 @@ func (tx *Transaction) To() *common.Address { return copyAddressPtr(tx.inner.to()) } +// IsL1MessageTx returns true if the transaction is an L1 cross-domain tx. +func (tx *Transaction) IsL1MessageTx() bool { + return tx.Type() == L1MessageTxType +} + +// AsL1MessageTx casts the tx into an L1 cross-domain tx. +func (tx *Transaction) AsL1MessageTx() *L1MessageTx { + if !tx.IsL1MessageTx() { + return nil + } + return tx.inner.(*L1MessageTx) +} + +// L1MessageQueueIndex returns the L1 queue index if `tx` is of type `L1MessageTx`. +// It returns 0 otherwise. +func (tx *Transaction) L1MessageQueueIndex() uint64 { + if !tx.IsL1MessageTx() { + return 0 + } + return tx.AsL1MessageTx().QueueIndex +} + // Cost returns gas * gasPrice + value. func (tx *Transaction) Cost() *big.Int { total := new(big.Int).Mul(tx.GasPrice(), new(big.Int).SetUint64(tx.Gas())) @@ -324,6 +352,9 @@ 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() { + return new(big.Int), nil + } if baseFee == nil { return tx.GasTipCap(), nil } @@ -563,48 +594,51 @@ func (t *TransactionsByPriceAndNonce) Pop() { // // NOTE: In a future PR this will be removed. type Message struct { - to *common.Address - from common.Address - nonce uint64 - amount *big.Int - gasLimit uint64 - gasPrice *big.Int - gasFeeCap *big.Int - gasTipCap *big.Int - data []byte - accessList AccessList - isFake bool + to *common.Address + from common.Address + nonce uint64 + amount *big.Int + gasLimit uint64 + gasPrice *big.Int + gasFeeCap *big.Int + gasTipCap *big.Int + data []byte + accessList AccessList + isFake bool + isL1MessageTx 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 { return Message{ - from: from, - to: to, - nonce: nonce, - amount: amount, - gasLimit: gasLimit, - gasPrice: gasPrice, - gasFeeCap: gasFeeCap, - gasTipCap: gasTipCap, - data: data, - accessList: accessList, - isFake: isFake, + from: from, + to: to, + nonce: nonce, + amount: amount, + gasLimit: gasLimit, + gasPrice: gasPrice, + gasFeeCap: gasFeeCap, + gasTipCap: gasTipCap, + data: data, + accessList: accessList, + isFake: isFake, + isL1MessageTx: false, } } // AsMessage returns the transaction as a core.Message. func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) { msg := Message{ - nonce: tx.Nonce(), - gasLimit: tx.Gas(), - gasPrice: new(big.Int).Set(tx.GasPrice()), - gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), - gasTipCap: new(big.Int).Set(tx.GasTipCap()), - to: tx.To(), - amount: tx.Value(), - data: tx.Data(), - accessList: tx.AccessList(), - isFake: false, + nonce: tx.Nonce(), + gasLimit: tx.Gas(), + gasPrice: new(big.Int).Set(tx.GasPrice()), + gasFeeCap: new(big.Int).Set(tx.GasFeeCap()), + gasTipCap: new(big.Int).Set(tx.GasTipCap()), + to: tx.To(), + amount: tx.Value(), + data: tx.Data(), + accessList: tx.AccessList(), + isFake: false, + isL1MessageTx: tx.IsL1MessageTx(), } // If baseFee provided, set gasPrice to effectiveGasPrice. if baseFee != nil { @@ -626,6 +660,7 @@ func (m Message) Nonce() uint64 { return m.nonce } 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 } // 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 4c62c1b88..fc031b9d8 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -48,6 +48,10 @@ type txJSON struct { // Only used for encoding: Hash common.Hash `json:"hash"` + + // L1 message transaction fields: + Sender common.Address `json:"sender,omitempty"` + QueueIndex *hexutil.Uint64 `json:"queueIndex,omitempty"` } // MarshalJSON marshals as JSON with a hash. @@ -94,6 +98,13 @@ func (t *Transaction) MarshalJSON() ([]byte, error) { enc.V = (*hexutil.Big)(tx.V) enc.R = (*hexutil.Big)(tx.R) enc.S = (*hexutil.Big)(tx.S) + case *L1MessageTx: + enc.QueueIndex = (*hexutil.Uint64)(&tx.QueueIndex) + enc.Gas = (*hexutil.Uint64)(&tx.Gas) + enc.To = t.To() + enc.Value = (*hexutil.Big)(tx.Value) + enc.Data = (*hexutil.Bytes)(&tx.Data) + enc.Sender = tx.Sender } return json.Marshal(&enc) } @@ -262,6 +273,29 @@ func (t *Transaction) UnmarshalJSON(input []byte) error { return err } } + case L1MessageTxType: + var itx L1MessageTx + inner = &itx + if dec.QueueIndex == nil { + return errors.New("missing required field 'queueIndex' in transaction") + } + itx.QueueIndex = uint64(*dec.QueueIndex) + if dec.Gas == nil { + return errors.New("missing required field 'gas' in transaction") + } + itx.Gas = uint64(*dec.Gas) + if dec.To != nil { + itx.To = dec.To + } + if dec.Value == nil { + return errors.New("missing required field 'value' in transaction") + } + itx.Value = (*big.Int)(dec.Value) + if dec.Data == nil { + return errors.New("missing required field 'input' in transaction") + } + itx.Data = *dec.Data + itx.Sender = dec.Sender default: return ErrTxTypeNotSupported diff --git a/core/types/transaction_signing.go b/core/types/transaction_signing.go index ff3c12ffa..3352e044c 100644 --- a/core/types/transaction_signing.go +++ b/core/types/transaction_signing.go @@ -182,6 +182,9 @@ func NewLondonSigner(chainId *big.Int) Signer { } func (s londonSigner) Sender(tx *Transaction) (common.Address, error) { + if tx.IsL1MessageTx() { + return tx.AsL1MessageTx().Sender, nil + } if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Sender(tx) } @@ -201,6 +204,9 @@ func (s londonSigner) Equal(s2 Signer) bool { } func (s londonSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) { + if tx.IsL1MessageTx() { + return nil, nil, nil, fmt.Errorf("l1 message tx do not have a signature") + } txdata, ok := tx.inner.(*DynamicFeeTx) if !ok { return s.eip2930Signer.SignatureValues(tx, sig) @@ -218,6 +224,9 @@ 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.IsL1MessageTx() { + panic("l1 message tx cannot be signed and do not have a signing hash") + } if tx.Type() != DynamicFeeTxType { return s.eip2930Signer.Hash(tx) } @@ -267,6 +276,7 @@ func (s eip2930Signer) Sender(tx *Transaction) (common.Address, error) { // id, add 27 to become equivalent to unprotected Homestead signatures. V = new(big.Int).Add(V, big.NewInt(27)) default: + // L1MessageTx not supported return common.Address{}, ErrTxTypeNotSupported } if tx.ChainId().Cmp(s.chainId) != 0 { @@ -288,6 +298,7 @@ func (s eip2930Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *bi R, S, _ = decodeSignature(sig) V = big.NewInt(int64(sig[64])) default: + // L1MessageTx not supported return nil, nil, nil, ErrTxTypeNotSupported } return R, S, V, nil diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go index 9fe007b48..e6cf5545b 100644 --- a/core/types/transaction_test.go +++ b/core/types/transaction_test.go @@ -419,7 +419,8 @@ func TestTransactionCoding(t *testing.T) { ) for i := uint64(0); i < 500; i++ { var txdata TxData - switch i % 5 { + var isL1MessageTx bool + switch i % 6 { case 0: // Legacy tx. txdata = &LegacyTx{ @@ -467,10 +468,27 @@ func TestTransactionCoding(t *testing.T) { GasPrice: big.NewInt(10), AccessList: accesses, } + case 5: + // L1MessageTx + isL1MessageTx = true + txdata = &L1MessageTx{ + QueueIndex: i, + Gas: 123457, + To: &recipient, + Value: big.NewInt(10), + Data: []byte("abcdef"), + Sender: addr, + } } - tx, err := SignNewTx(key, signer, txdata) - if err != nil { - t.Fatalf("could not sign transaction: %v", err) + var tx *Transaction + // dont sign L1MessageTx + if isL1MessageTx { + tx = NewTx(txdata) + } else { + tx, err = SignNewTx(key, signer, txdata) + if err != nil { + t.Fatalf("could not sign transaction: %v", err) + } } // RLP parsedTx, err := encodeDecodeBinary(tx) @@ -488,6 +506,27 @@ func TestTransactionCoding(t *testing.T) { } } +// make sure that the transaction hash is same as bridge contract +// go test -v -run TestBridgeTxHash +func TestBridgeTxHash(t *testing.T) { + sender := common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266") + to := common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8") + tx := NewTx( + &L1MessageTx{ + Sender: sender, + QueueIndex: 1, + Value: big.NewInt(2), + Gas: 3, + To: &to, + Data: []byte{1, 2, 3, 4}, + }, + ) + // assert equal + if tx.Hash() != common.HexToHash("0x1cebed6d90ef618f60eec1b7edc0df36b298a237c219f0950081acfb72eac6be") { + t.Errorf("hash does not match bridge contract") + } +} + func encodeDecodeJSON(tx *Transaction) (*Transaction, error) { data, err := json.Marshal(tx) if err != nil { diff --git a/eth/api.go b/eth/api.go index 2e9b91246..9c7779c01 100644 --- a/eth/api.go +++ b/eth/api.go @@ -607,3 +607,43 @@ func (api *PrivateDebugAPI) GetAccessibleState(from, to rpc.BlockNumber) (uint64 } return 0, fmt.Errorf("No state found") } + +// ScrollAPI provides private RPC methods to query the L1 message database. +type ScrollAPI struct { + eth *Ethereum +} + +// NewScrollAPI creates a new RPC service to query the L1 message database. +func NewScrollAPI(eth *Ethereum) *ScrollAPI { + return &ScrollAPI{eth: eth} +} + +// GetL1SyncHeight returns the latest synced L1 block height from the local database. +func (api *ScrollAPI) GetL1SyncHeight(ctx context.Context) (height *uint64, err error) { + return rawdb.ReadSyncedL1BlockNumber(api.eth.ChainDb()), nil +} + +// GetL1MessageByIndex queries an L1 message by its index in the local database. +func (api *ScrollAPI) GetL1MessageByIndex(ctx context.Context, queueIndex uint64) (height *types.L1MessageTx, err error) { + return rawdb.ReadL1Message(api.eth.ChainDb(), queueIndex), nil +} + +// GetFirstQueueIndexNotInL2Block returns the first L1 message queue index that is +// not included in the chain up to and including the provided block. +func (api *ScrollAPI) GetFirstQueueIndexNotInL2Block(ctx context.Context, hash common.Hash) (queueIndex *uint64, err error) { + return rawdb.ReadFirstQueueIndexNotInL2Block(api.eth.ChainDb(), hash), nil +} + +// GetLatestRelayedQueueIndex returns the highest L1 message queue index included in the canonical chain. +func (api *ScrollAPI) GetLatestRelayedQueueIndex(ctx context.Context) (queueIndex *uint64, err error) { + block := api.eth.blockchain.CurrentBlock() + queueIndex, err = api.GetFirstQueueIndexNotInL2Block(ctx, block.Hash()) + if queueIndex == nil || err != nil { + return queueIndex, err + } + if *queueIndex == 0 { + return nil, nil + } + lastIncluded := *queueIndex - 1 + return &lastIncluded, nil +} diff --git a/eth/backend.go b/eth/backend.go index f0796b921..44e2cfe57 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -18,6 +18,7 @@ package eth import ( + "context" "errors" "fmt" "math/big" @@ -54,6 +55,7 @@ import ( "github.com/scroll-tech/go-ethereum/p2p/enode" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -67,6 +69,7 @@ type Ethereum struct { // Handlers txPool *core.TxPool + syncService *sync_service.SyncService blockchain *core.BlockChain handler *handler ethDialCandidates enode.Iterator @@ -99,7 +102,7 @@ type Ethereum struct { // New creates a new Ethereum object (including the // initialisation of the common Ethereum object) -func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { +func New(stack *node.Node, config *ethconfig.Config, l1Client sync_service.EthClient) (*Ethereum, error) { // Ensure configuration values are compatible and sane if config.SyncMode == downloader.LightSync { return nil, errors.New("can't run eth.Ethereum in light sync mode, use les.LightEthereum") @@ -206,6 +209,13 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { } 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) + if err != nil { + return nil, fmt.Errorf("cannot initialize L1 sync service: %w", err) + } + eth.syncService.Start() + // Permit the downloader to use the trie cache allowance during fast sync cacheLimit := cacheConfig.TrieCleanLimit + cacheConfig.TrieDirtyLimit + cacheConfig.SnapshotLimit checkpoint := config.Checkpoint @@ -343,6 +353,11 @@ func (s *Ethereum) APIs() []rpc.API { Version: "1.0", Service: s.netRPCService, Public: true, + }, { + Namespace: "scroll", + Version: "1.0", + Service: NewScrollAPI(s), + Public: false, }, }...) } @@ -500,17 +515,18 @@ func (s *Ethereum) StopMining() { func (s *Ethereum) IsMining() bool { return s.miner.Mining() } func (s *Ethereum) Miner() *miner.Miner { return s.miner } -func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } -func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } -func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } -func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } -func (s *Ethereum) Engine() consensus.Engine { return s.engine } -func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } -func (s *Ethereum) IsListening() bool { return true } // Always listening -func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } -func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } -func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } -func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } +func (s *Ethereum) AccountManager() *accounts.Manager { return s.accountManager } +func (s *Ethereum) BlockChain() *core.BlockChain { return s.blockchain } +func (s *Ethereum) TxPool() *core.TxPool { return s.txPool } +func (s *Ethereum) EventMux() *event.TypeMux { return s.eventMux } +func (s *Ethereum) Engine() consensus.Engine { return s.engine } +func (s *Ethereum) ChainDb() ethdb.Database { return s.chainDb } +func (s *Ethereum) IsListening() bool { return true } // Always listening +func (s *Ethereum) Downloader() *downloader.Downloader { return s.handler.downloader } +func (s *Ethereum) Synced() bool { return atomic.LoadUint32(&s.handler.acceptTxs) == 1 } +func (s *Ethereum) ArchiveMode() bool { return s.config.NoPruning } +func (s *Ethereum) BloomIndexer() *core.ChainIndexer { return s.bloomIndexer } +func (s *Ethereum) SyncService() *sync_service.SyncService { return s.syncService } // Protocols returns all the currently configured // network protocols to start. @@ -555,6 +571,7 @@ func (s *Ethereum) Stop() error { s.bloomIndexer.Close() close(s.closeBloomHandler) s.txPool.Stop() + s.syncService.Stop() s.miner.Close() s.blockchain.Stop() s.engine.Close() diff --git a/eth/catalyst/api_test.go b/eth/catalyst/api_test.go index d601333c9..6853b5ba7 100644 --- a/eth/catalyst/api_test.go +++ b/eth/catalyst/api_test.go @@ -233,7 +233,7 @@ func startEthService(t *testing.T, genesis *core.Genesis, blocks []*types.Block) } ethcfg := ðconfig.Config{Genesis: genesis, Ethash: ethash.Config{PowMode: ethash.ModeFake}} - ethservice, err := eth.New(n, ethcfg) + ethservice, err := eth.New(n, ethcfg, nil) if err != nil { t.Fatal("can't create eth service:", err) } diff --git a/eth/handler.go b/eth/handler.go index 4215e483e..91adb2a62 100644 --- a/eth/handler.go +++ b/eth/handler.go @@ -479,6 +479,10 @@ 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() { + continue + } peers := h.peers.peersWithoutTransaction(tx.Hash()) // Send the tx unconditionally to a subset of our peers numDirect := int(math.Sqrt(float64(len(peers)))) diff --git a/eth/sync_test.go b/eth/sync_test.go index 105f76efe..2305cb9e8 100644 --- a/eth/sync_test.go +++ b/eth/sync_test.go @@ -21,7 +21,6 @@ import ( "testing" "time" - "github.com/scroll-tech/go-ethereum/eth/downloader" "github.com/scroll-tech/go-ethereum/eth/protocols/eth" "github.com/scroll-tech/go-ethereum/p2p" "github.com/scroll-tech/go-ethereum/p2p/enode" @@ -69,11 +68,12 @@ func testFastSyncDisabling(t *testing.T, protocol uint) { time.Sleep(250 * time.Millisecond) // Check that fast sync was disabled - op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD()) - if err := empty.handler.doSync(op); err != nil { - t.Fatal("sync failed:", err) - } - if atomic.LoadUint32(&empty.handler.fastSync) == 1 { - t.Fatalf("fast sync not disabled after successful synchronisation") - } + // TODO: Adjust L1MessageTx insertion logic to work with fast/snap sync + // op := peerToSyncOp(downloader.FastSync, empty.handler.peers.peerWithHighestTD()) + // if err := empty.handler.doSync(op); err != nil { + // t.Fatal("sync failed:", err) + // } + // if atomic.LoadUint32(&empty.handler.fastSync) == 1 { + // t.Fatalf("fast sync not disabled after successful synchronisation") + // } } diff --git a/ethclient/ethclient_test.go b/ethclient/ethclient_test.go index 2ca853a16..d89bbcb50 100644 --- a/ethclient/ethclient_test.go +++ b/ethclient/ethclient_test.go @@ -236,7 +236,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Create Ethereum Service config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake - ethservice, err := eth.New(n, config) + ethservice, err := eth.New(n, config, nil) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) } diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index cdbd50502..32a20597f 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -57,7 +57,7 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { // Create Ethereum Service config := ðconfig.Config{Genesis: genesis} config.Ethash.PowMode = ethash.ModeFake - ethservice, err := eth.New(n, config) + ethservice, err := eth.New(n, config, nil) if err != nil { t.Fatalf("can't create new ethereum service: %v", err) } diff --git a/ethdb/memorydb/memorydb.go b/ethdb/memorydb/memorydb.go index 8406526e0..ca89dbe6d 100644 --- a/ethdb/memorydb/memorydb.go +++ b/ethdb/memorydb/memorydb.go @@ -32,9 +32,9 @@ var ( // invocation of a data access operation. errMemorydbClosed = errors.New("database closed") - // errMemorydbNotFound is returned if a key is requested that is not found in + // ErrMemorydbNotFound is returned if a key is requested that is not found in // the provided memory database. - errMemorydbNotFound = errors.New("not found") + ErrMemorydbNotFound = errors.New("not found") ) // Database is an ephemeral key-value store. Apart from basic data storage @@ -94,7 +94,7 @@ func (db *Database) Get(key []byte) ([]byte, error) { if entry, ok := db.db[string(key)]; ok { return common.CopyBytes(entry), nil } - return nil, errMemorydbNotFound + return nil, ErrMemorydbNotFound } // Put inserts the given value into the key-value store. diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 8f69c688c..be2867484 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -253,7 +253,7 @@ func createGQLService(t *testing.T, stack *node.Node) { TrieTimeout: 60 * time.Minute, SnapshotCache: 5, } - ethBackend, err := eth.New(stack, ethConf) + ethBackend, err := eth.New(stack, ethConf, nil) if err != nil { t.Fatalf("could not create eth backend: %v", err) } @@ -311,7 +311,7 @@ func createGQLServiceWithTransactions(t *testing.T, stack *node.Node) { SnapshotCache: 5, } - ethBackend, err := eth.New(stack, ethConf) + ethBackend, err := eth.New(stack, ethConf, nil) if err != nil { t.Fatalf("could not create eth backend: %v", err) } diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index c4bdbaeb8..777738d4d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -30,6 +30,7 @@ var Modules = map[string]string{ "txpool": TxpoolJs, "les": LESJs, "vflux": VfluxJs, + "scroll": ScrollJs, } const CliqueJs = ` @@ -848,3 +849,37 @@ web3._extend({ ] }); ` + +const ScrollJs = ` +web3._extend({ + property: 'scroll', + methods: [ + new web3._extend.Method({ + name: 'getBlockTraceByNumberOrHash', + call: 'scroll_getBlockTraceByNumberOrHash', + params: 1 + }), + new web3._extend.Method({ + name: 'getL1MessageByIndex', + call: 'scroll_getL1MessageByIndex', + params: 1 + }), + new web3._extend.Method({ + name: 'getFirstQueueIndexNotInL2Block', + call: 'scroll_getFirstQueueIndexNotInL2Block', + params: 1 + }) + ], + properties: + [ + new web3._extend.Property({ + name: 'l1SyncHeight', + getter: 'scroll_getL1SyncHeight' + }), + new web3._extend.Property({ + name: 'latestRelayedQueueIndex', + getter: 'scroll_getLatestRelayedQueueIndex' + }) + ] +}); +` diff --git a/les/api_test.go b/les/api_test.go index 1e9eeb626..ad47ff02d 100644 --- a/les/api_test.go +++ b/les/api_test.go @@ -506,7 +506,7 @@ func newLesServerService(ctx *adapters.ServiceContext, stack *node.Node) (node.L config.SyncMode = (ethdownloader.SyncMode)(downloader.FullSync) config.LightServ = testServerCapacity config.LightPeers = testMaxClients - ethereum, err := eth.New(stack, &config) + ethereum, err := eth.New(stack, &config, nil) if err != nil { return nil, err } diff --git a/miner/miner.go b/miner/miner.go index 5edc72e1d..b46fbdb85 100644 --- a/miner/miner.go +++ b/miner/miner.go @@ -30,15 +30,19 @@ import ( "github.com/scroll-tech/go-ethereum/core/state" "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/eth/downloader" + "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/params" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" ) // Backend wraps all methods required for mining. type Backend interface { BlockChain() *core.BlockChain TxPool() *core.TxPool + ChainDb() ethdb.Database + SyncService() *sync_service.SyncService } // Config is the configuration parameters of mining. diff --git a/miner/miner_test.go b/miner/miner_test.go index 56a3ec079..a83fc2c8b 100644 --- a/miner/miner_test.go +++ b/miner/miner_test.go @@ -29,20 +29,24 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/eth/downloader" + "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/ethdb/memorydb" "github.com/scroll-tech/go-ethereum/event" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" "github.com/scroll-tech/go-ethereum/trie" ) type mockBackend struct { - bc *core.BlockChain - txPool *core.TxPool + bc *core.BlockChain + txPool *core.TxPool + chainDb ethdb.Database } -func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool) *mockBackend { +func NewMockBackend(bc *core.BlockChain, txPool *core.TxPool, chainDb ethdb.Database) *mockBackend { return &mockBackend{ - bc: bc, - txPool: txPool, + bc: bc, + txPool: txPool, + chainDb: chainDb, } } @@ -54,6 +58,14 @@ func (m *mockBackend) TxPool() *core.TxPool { return m.txPool } +func (m *mockBackend) SyncService() *sync_service.SyncService { + return nil +} + +func (m *mockBackend) ChainDb() ethdb.Database { + return m.chainDb +} + type testBlockChain struct { statedb *state.StateDB gasLimit uint64 @@ -253,7 +265,7 @@ func createMiner(t *testing.T) (*Miner, *event.TypeMux) { blockchain := &testBlockChain{statedb, 10000000, new(event.Feed)} pool := core.NewTxPool(testTxPoolConfig, chainConfig, blockchain) - backend := NewMockBackend(bc, pool) + backend := NewMockBackend(bc, pool, chainDB) // Create event Mux mux := new(event.TypeMux) // Create Miner diff --git a/miner/stress/1559/main.go b/miner/stress/1559/main.go index b3772102d..a00b2d457 100644 --- a/miner/stress/1559/main.go +++ b/miner/stress/1559/main.go @@ -255,7 +255,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { GasPrice: big.NewInt(1), Recommit: time.Second, }, - }) + }, nil) if err != nil { return nil, nil, err } diff --git a/miner/stress/clique/main.go b/miner/stress/clique/main.go index fd5b96962..3fc352023 100644 --- a/miner/stress/clique/main.go +++ b/miner/stress/clique/main.go @@ -214,7 +214,7 @@ func makeSealer(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { GasPrice: big.NewInt(1), Recommit: time.Second, }, - }) + }, nil) if err != nil { return nil, nil, err } diff --git a/miner/stress/ethash/main.go b/miner/stress/ethash/main.go index b8a9d4c06..e4af6a5c9 100644 --- a/miner/stress/ethash/main.go +++ b/miner/stress/ethash/main.go @@ -185,7 +185,7 @@ func makeMiner(genesis *core.Genesis) (*node.Node, *eth.Ethereum, error) { GasPrice: big.NewInt(1), Recommit: time.Second, }, - }) + }, nil) if err != nil { return nil, nil, err } diff --git a/miner/worker.go b/miner/worker.go index c6484c347..44cb3e525 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -30,6 +30,7 @@ import ( "github.com/scroll-tech/go-ethereum/consensus" "github.com/scroll-tech/go-ethereum/consensus/misc" "github.com/scroll-tech/go-ethereum/core" + "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/event" @@ -88,6 +89,7 @@ type environment struct { uncles mapset.Set // uncle set tcount int // tx count in cycle blockSize common.StorageSize // approximate size of tx payload in bytes + l1TxCount int // l1 msg count in cycle gasPool *core.GasPool // available gas used to pack transactions header *types.Header @@ -142,6 +144,8 @@ type worker struct { chainHeadSub event.Subscription chainSideCh chan core.ChainSideEvent chainSideSub event.Subscription + l1MsgsCh chan core.NewL1MsgsEvent + l1MsgsSub event.Subscription // Channels newWorkCh chan *newWorkReq @@ -172,8 +176,9 @@ 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. + 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. // 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). @@ -206,6 +211,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth), pendingTasks: make(map[common.Hash]*task), txsCh: make(chan core.NewTxsEvent, txChanSize), + l1MsgsCh: make(chan core.NewL1MsgsEvent, txChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), newWorkCh: make(chan *newWorkReq), @@ -216,8 +222,21 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus resubmitIntervalCh: make(chan time.Duration), resubmitAdjustCh: make(chan *intervalAdjust, resubmitAdjustChanSize), } + // Subscribe NewTxsEvent for tx pool worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) + + // Subscribe NewL1MsgsEvent for sync service + if s := eth.SyncService(); s != nil { + worker.l1MsgsSub = s.SubscribeNewL1MsgsEvent(worker.l1MsgsCh) + } else { + // create an empty subscription so that the tests won't fail + worker.l1MsgsSub = 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) @@ -376,6 +395,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { } timer.Reset(recommit) atomic.StoreInt32(&w.newTxs, 0) + atomic.StoreInt32(&w.newL1Msgs, 0) } // clearPending cleans the stale pending tasks. clearPending := func(number uint64) { @@ -405,7 +425,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 { + if atomic.LoadInt32(&w.newTxs) == 0 && atomic.LoadInt32(&w.newL1Msgs) == 0 { timer.Reset(recommit) continue } @@ -452,6 +472,7 @@ func (w *worker) newWorkLoop(recommit time.Duration) { func (w *worker) mainLoop() { defer w.wg.Done() defer w.txsSub.Unsubscribe() + defer w.l1MsgsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() defer w.chainSideSub.Unsubscribe() defer func() { @@ -543,11 +564,16 @@ func (w *worker) mainLoop() { } atomic.AddInt32(&w.newTxs, int32(len(ev.Txs))) + case ev := <-w.l1MsgsCh: + atomic.AddInt32(&w.newL1Msgs, int32(ev.Count)) + // System stopped case <-w.exitCh: return case <-w.txsSub.Err(): return + case <-w.l1MsgsSub.Err(): + return case <-w.chainHeadSub.Err(): return case <-w.chainSideSub.Err(): @@ -709,6 +735,7 @@ func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error { // Keep track of transactions which return errors so they can be removed env.tcount = 0 env.blockSize = 0 + env.l1TxCount = 0 // Swap out the old work with the new one, terminating any leftover prefetcher // processes in the mean time and starting a new one. @@ -821,8 +848,8 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return atomic.LoadInt32(interrupt) == commitInterruptNewHead } // If we have collected enough transactions then we're done - if !w.chainConfig.Scroll.IsValidTxCount(w.current.tcount + 1) { - log.Trace("Transaction count limit reached", "have", w.current.tcount, "want", w.chainConfig.Scroll.MaxTxPerBlock) + if !w.chainConfig.Scroll.IsValidL2TxCount(w.current.tcount - w.current.l1TxCount + 1) { + log.Trace("Transaction count limit reached", "have", w.current.tcount-w.current.l1TxCount, "want", w.chainConfig.Scroll.MaxTxPerBlock) break } // If we don't have enough gas for any further transactions then we're done @@ -876,6 +903,9 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin case errors.Is(err, nil): // Everything ok, collect the logs and shift in the next transaction from the same account coalescedLogs = append(coalescedLogs, logs...) + if tx.IsL1MessageTx() { + w.current.l1TxCount++ + } w.current.tcount++ w.current.blockSize += tx.Size() txs.Shift() @@ -916,6 +946,17 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin return false } +func (w *worker) collectPendingL1Messages() []types.L1MessageTx { + nextQueueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(w.eth.ChainDb(), w.chain.CurrentHeader().Hash()) + if nextQueueIndex == nil { + // the parent (w.chain.CurrentHeader) must have been processed before we start a new mining job. + log.Crit("Failed to read last L1 message in L2 block", "l2BlockHash", w.chain.CurrentHeader().Hash()) + } + startIndex := *nextQueueIndex + maxCount := w.chainConfig.Scroll.L1Config.NumL1MessagesPerBlock + return rawdb.ReadL1MessagesFrom(w.eth.ChainDb(), startIndex, maxCount) +} + // commitNewWork generates several new sealing tasks based on the parent block. func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) { w.mu.RLock() @@ -1016,13 +1057,30 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) if !noempty && atomic.LoadUint32(&w.noempty) == 0 { w.commit(uncles, nil, false, tstart) } - + // fetch l1Txs + l1Txs := make(map[common.Address]types.Transactions) + pendingL1Txs := 0 + if w.chainConfig.Scroll.ShouldIncludeL1Messages() { + l1Messages := w.collectPendingL1Messages() + pendingL1Txs = len(l1Messages) + for _, l1msg := range l1Messages { + tx := types.NewTx(&l1msg) + sender := l1msg.Sender + senderTxs, ok := l1Txs[sender] + if ok { + senderTxs = append(senderTxs, tx) + l1Txs[sender] = senderTxs + } else { + l1Txs[sender] = types.Transactions{tx} + } + } + } // Fill the block with all available pending transactions. pending := w.eth.TxPool().Pending(true) // 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 && atomic.LoadUint32(&w.noempty) == 0 { + if len(pending) == 0 && pendingL1Txs == 0 && atomic.LoadUint32(&w.noempty) == 0 { w.updateSnapshot() return } @@ -1034,6 +1092,13 @@ func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64) localTxs[account] = txs } } + if w.chainConfig.Scroll.ShouldIncludeL1Messages() && len(l1Txs) > 0 { + log.Trace("Processing L1 messages for inclusion", "count", pendingL1Txs) + txs := types.NewTransactionsByPriceAndNonce(w.current.signer, l1Txs, header.BaseFee) + if w.commitTransactions(txs, w.coinbase, interrupt) { + return + } + } if len(localTxs) > 0 { txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs, header.BaseFee) if w.commitTransactions(txs, w.coinbase, interrupt) { diff --git a/miner/worker_test.go b/miner/worker_test.go index 8b1a49006..5352dd9f8 100644 --- a/miner/worker_test.go +++ b/miner/worker_test.go @@ -23,6 +23,8 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/scroll-tech/go-ethereum/accounts" "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/consensus" @@ -36,6 +38,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/sync_service" ) const ( @@ -166,8 +169,10 @@ func newTestWorkerBackend(t *testing.T, chainConfig *params.ChainConfig, engine } } -func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } -func (b *testWorkerBackend) TxPool() *core.TxPool { return b.txPool } +func (b *testWorkerBackend) BlockChain() *core.BlockChain { return b.chain } +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) newRandomUncle() *types.Block { var parent *types.Block @@ -525,3 +530,204 @@ func testAdjustInterval(t *testing.T, chainConfig *params.ChainConfig, engine co t.Error("interval reset timeout") } } + +func TestGenerateBlockWithL1MsgEthash(t *testing.T) { + testGenerateBlockWithL1Msg(t, false) +} + +func TestGenerateBlockWithL1MsgClique(t *testing.T) { + testGenerateBlockWithL1Msg(t, true) +} + +func testGenerateBlockWithL1Msg(t *testing.T, isClique bool) { + assert := assert.New(t) + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{3}, Data: []byte{0x01}, Sender: common.Address{4}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}} + rawdb.WriteL1Messages(db, msgs) + + if isClique { + chainConfig = params.AllCliqueProtocolChanges + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + } else { + chainConfig = params.AllEthashProtocolChanges + engine = ethash.NewFaker() + } + chainConfig.Scroll.L1Config = ¶ms.L1Config{ + NumL1MessagesPerBlock: 1, + } + + chainConfig.LondonBlock = big.NewInt(0) + w, b := newTestWorker(t, chainConfig, engine, db, 0) + defer w.close() + + // This test chain imports the mined blocks. + b.genesis.MustCommit(db) + chain, _ := core.NewBlockChain(db, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Start mining! + w.start() + + for i := 0; i < 2; i++ { + + select { + case ev := <-sub.Chan(): + block := ev.Data.(core.NewMinedBlockEvent).Block + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + assert.Equal(1, len(block.Transactions())) + + queueIndex := rawdb.ReadFirstQueueIndexNotInL2Block(db, block.Hash()) + assert.NotNil(queueIndex) + assert.Equal(uint64(i+1), *queueIndex) + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } + } +} + +func TestExcludeL1MsgFromTxlimit(t *testing.T) { + assert := assert.New(t) + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + chainConfig = params.AllCliqueProtocolChanges + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + + // Set maxTxPerBlock = 2 and NumL1MessagesPerBlock = 2 + maxTxPerBlock := 2 + chainConfig.Scroll.MaxTxPerBlock = &maxTxPerBlock + chainConfig.Scroll.L1Config = ¶ms.L1Config{ + NumL1MessagesPerBlock: 2, + } + + // Insert 2 l1msgs + l1msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{3}, Data: []byte{0x01}, Sender: common.Address{4}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}} + rawdb.WriteL1Messages(db, l1msgs) + + chainConfig.LondonBlock = big.NewInt(0) + w, b := newTestWorker(t, chainConfig, engine, db, 0) + defer w.close() + + // This test chain imports the mined blocks. + b.genesis.MustCommit(db) + chain, _ := core.NewBlockChain(db, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Insert 2 non-l1msg txs + b.txPool.AddLocal(b.newRandomTx(true)) + b.txPool.AddLocal(b.newRandomTx(false)) + + // Start mining! + w.start() + + select { + case ev := <-sub.Chan(): + block := ev.Data.(core.NewMinedBlockEvent).Block + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + assert.Equal(4, len(block.Transactions())) + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } +} + +func TestL1MsgCorrectOrder(t *testing.T) { + assert := assert.New(t) + var ( + engine consensus.Engine + chainConfig *params.ChainConfig + db = rawdb.NewMemoryDatabase() + ) + chainConfig = params.AllCliqueProtocolChanges + chainConfig.Clique = ¶ms.CliqueConfig{Period: 1, Epoch: 30000} + engine = clique.New(chainConfig.Clique, db) + + chainConfig.Scroll.L1Config = ¶ms.L1Config{ + NumL1MessagesPerBlock: 10, + } + + // Insert 3 l1msgs + l1msgs := []types.L1MessageTx{ + {QueueIndex: 0, Gas: 21016, To: &common.Address{3}, Data: []byte{0x01}, Sender: common.Address{4}}, + {QueueIndex: 1, Gas: 21016, To: &common.Address{1}, Data: []byte{0x01}, Sender: common.Address{2}}, + {QueueIndex: 2, Gas: 21016, To: &common.Address{3}, Data: []byte{0x01}, Sender: common.Address{4}}} + rawdb.WriteL1Messages(db, l1msgs) + + chainConfig.LondonBlock = big.NewInt(0) + w, b := newTestWorker(t, chainConfig, engine, db, 0) + defer w.close() + + // This test chain imports the mined blocks. + b.genesis.MustCommit(db) + chain, _ := core.NewBlockChain(db, nil, b.chain.Config(), engine, vm.Config{ + Debug: true, + Tracer: vm.NewStructLogger(&vm.LogConfig{EnableMemory: true, EnableReturnData: true})}, nil, nil) + defer chain.Stop() + + // Ignore empty commit here for less noise. + w.skipSealHook = func(task *task) bool { + return len(task.receipts) == 0 + } + + // Wait for mined blocks. + sub := w.mux.Subscribe(core.NewMinedBlockEvent{}) + defer sub.Unsubscribe() + + // Insert local tx + b.txPool.AddLocal(b.newRandomTx(true)) + + // Start mining! + w.start() + + select { + case ev := <-sub.Chan(): + block := ev.Data.(core.NewMinedBlockEvent).Block + if _, err := chain.InsertChain([]*types.Block{block}); err != nil { + t.Fatalf("failed to insert new mined block %d: %v", block.NumberU64(), err) + } + assert.Equal(4, len(block.Transactions())) + assert.True(block.Transactions()[0].IsL1MessageTx() && block.Transactions()[1].IsL1MessageTx() && block.Transactions()[2].IsL1MessageTx()) + assert.Equal(uint64(0), block.Transactions()[0].AsL1MessageTx().QueueIndex) + assert.Equal(uint64(1), block.Transactions()[1].AsL1MessageTx().QueueIndex) + assert.Equal(uint64(2), block.Transactions()[2].AsL1MessageTx().QueueIndex) + case <-time.After(3 * time.Second): + t.Fatalf("timeout") + } +} diff --git a/node/config.go b/node/config.go index fad1ddcaf..439b11a2f 100644 --- a/node/config.go +++ b/node/config.go @@ -190,6 +190,13 @@ type Config struct { // AllowUnprotectedTxs allows non EIP-155 protected transactions to be send over RPC. AllowUnprotectedTxs bool `toml:",omitempty"` + + // Endpoint of L1 HTTP-RPC server + L1Endpoint string `toml:",omitempty"` + // Number of confirmations on L1 needed for finalization + L1Confirmations rpc.BlockNumber `toml:",omitempty"` + // L1 bridge deployment block number + L1DeploymentBlock uint64 `toml:",omitempty"` } // IPCEndpoint resolves an IPC endpoint based on a configured value, taking into diff --git a/params/config.go b/params/config.go index df257a60e..e84e7361f 100644 --- a/params/config.go +++ b/params/config.go @@ -288,6 +288,11 @@ var ( FeeVaultAddress: &rcfg.ScrollFeeVaultAddress, EnableEIP2718: false, EnableEIP1559: false, + L1Config: &L1Config{ + L1ChainId: 5, + L1MessageQueueAddress: common.HexToAddress("0x79DB48002Aa861C8cb189cabc21c6B1468BC83BB"), + NumL1MessagesPerBlock: 0, + }, }, } @@ -304,6 +309,7 @@ var ( EnableEIP1559: true, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0}, }} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced @@ -319,6 +325,7 @@ var ( EnableEIP1559: true, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0}, }} 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), nil, nil, new(EthashConfig), nil, @@ -329,6 +336,7 @@ var ( EnableEIP1559: true, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0}, }} TestRules = TestChainConfig.Rules(new(big.Int)) @@ -340,6 +348,7 @@ var ( EnableEIP1559: true, MaxTxPerBlock: nil, MaxTxPayloadBytesPerBlock: nil, + L1Config: &L1Config{5, common.HexToAddress("0x0000000000000000000000000000000000000000"), 0}, }} ) @@ -452,6 +461,25 @@ type ScrollConfig struct { // Enable EIP-1559 in tx pool, EnableEIP2718 should be true too [optional] EnableEIP1559 bool `json:"enableEIP1559,omitempty"` + + // L1 config + L1Config *L1Config `json:"l1Config,omitempty"` +} + +// L1Config contains the l1 parameters needed to collect l1 messages in the sequencer +type L1Config struct { + L1ChainId uint64 `json:"l1ChainId,string,omitempty"` + L1MessageQueueAddress common.Address `json:"l1MessageQueueAddress,omitempty"` + NumL1MessagesPerBlock uint64 `json:"numL1MessagesPerBlock,string,omitempty"` +} + +func (c *L1Config) String() string { + if c == nil { + return "" + } + + return fmt.Sprintf("{l1ChainId: %v, l1MessageQueueAddress: %v, numL1MessagesPerBlock: %v}", + c.L1ChainId, c.L1MessageQueueAddress, c.NumL1MessagesPerBlock) } func (s ScrollConfig) BaseFeeEnabled() bool { @@ -466,6 +494,10 @@ func (s ScrollConfig) ZktrieEnabled() bool { return s.UseZktrie } +func (s ScrollConfig) ShouldIncludeL1Messages() bool { + return s.L1Config != nil && s.L1Config.NumL1MessagesPerBlock > 0 +} + func (s ScrollConfig) String() string { maxTxPerBlock := "" if s.MaxTxPerBlock != nil { @@ -477,12 +509,13 @@ func (s ScrollConfig) String() string { maxTxPayloadBytesPerBlock = fmt.Sprintf("%v", *s.MaxTxPayloadBytesPerBlock) } - return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, enableEIP2718:%v, enableEIP1559:%v}", - s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.EnableEIP2718, s.EnableEIP1559) + return fmt.Sprintf("{useZktrie: %v, maxTxPerBlock: %v, MaxTxPayloadBytesPerBlock: %v, feeVaultAddress: %v, enableEIP2718: %v, enableEIP1559: %v, l1Config: %v}", + s.UseZktrie, maxTxPerBlock, maxTxPayloadBytesPerBlock, s.FeeVaultAddress, s.EnableEIP2718, s.EnableEIP1559, s.L1Config.String()) } -// IsValidTxCount returns whether the given block's transaction count is below the limit. -func (s ScrollConfig) IsValidTxCount(count int) bool { +// IsValidL2TxCount returns whether the given block's L2 transaction count is below the limit. +// This limit corresponds to the number of ECDSA signature checks that we can fit into the zkEVM. +func (s ScrollConfig) IsValidL2TxCount(count int) bool { return s.MaxTxPerBlock == nil || count <= *s.MaxTxPerBlock } diff --git a/params/version.go b/params/version.go index 94a96e6d6..f61723a51 100644 --- a/params/version.go +++ b/params/version.go @@ -23,8 +23,8 @@ import ( const ( VersionMajor = 4 // Major version component of the current release - VersionMinor = 0 // Minor version component of the current release - VersionPatch = 4 // Patch version component of the current release + VersionMinor = 1 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) diff --git a/rollup/sync_service/bindings.go b/rollup/sync_service/bindings.go new file mode 100644 index 000000000..2e1cab372 --- /dev/null +++ b/rollup/sync_service/bindings.go @@ -0,0 +1,150 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +// generated using: +// forge flatten src/L1/rollup/L1MessageQueue.sol > flatten.sol +// go run github.com/scroll-tech/go-ethereum/cmd/abigen@develop --sol flatten.sol --pkg rollup --out ./L1MessageQueue.go --contract L1MessageQueue + +package sync_service + +import ( + "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" +) + +// L1MessageQueueMetaData contains all meta data concerning the L1MessageQueue contract. +var L1MessageQueueMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"startIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"count\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"skippedBitmap\",\"type\":\"uint256\"}],\"name\":\"DequeueTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"target\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"queueIndex\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"gasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"data\",\"type\":\"bytes\"}],\"name\":\"QueueTransaction\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_oldGateway\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newGateway\",\"type\":\"address\"}],\"name\":\"UpdateEnforcedTxGateway\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_oldGasOracle\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_newGasOracle\",\"type\":\"address\"}],\"name\":\"UpdateGasOracle\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_oldMaxGasLimit\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newMaxGasLimit\",\"type\":\"uint256\"}],\"name\":\"UpdateMaxGasLimit\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"appendCrossDomainMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"appendEnforcedTransaction\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_calldata\",\"type\":\"bytes\"}],\"name\":\"calculateIntrinsicGasFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_sender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_queueIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_value\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_target\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"computeTransactionHash\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"enforcedTxGateway\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_gasLimit\",\"type\":\"uint256\"}],\"name\":\"estimateCrossDomainMessageFee\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"gasOracle\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_queueIndex\",\"type\":\"uint256\"}],\"name\":\"getCrossDomainMessage\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_messenger\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_scrollChain\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_enforcedTxGateway\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_gasOracle\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_maxGasLimit\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxGasLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"messageQueue\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"messenger\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"nextCrossDomainMessageIndex\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingQueueIndex\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_startIndex\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_count\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_skippedBitmap\",\"type\":\"uint256\"}],\"name\":\"popCrossDomainMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"scrollChain\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGateway\",\"type\":\"address\"}],\"name\":\"updateEnforcedTxGateway\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_newGasOracle\",\"type\":\"address\"}],\"name\":\"updateGasOracle\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_newMaxGasLimit\",\"type\":\"uint256\"}],\"name\":\"updateMaxGasLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", +} + +// L1MessageQueueABI is the input ABI used to generate the binding from. +// Deprecated: Use L1MessageQueueMetaData.ABI instead. +var L1MessageQueueABI = L1MessageQueueMetaData.ABI + +// L1MessageQueueFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type L1MessageQueueFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// NewL1MessageQueueFilterer creates a new log filterer instance of L1MessageQueue, bound to a specific deployed contract. +func NewL1MessageQueueFilterer(address common.Address, filterer bind.ContractFilterer) (*L1MessageQueueFilterer, error) { + contract, err := bindL1MessageQueue(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &L1MessageQueueFilterer{contract: contract}, nil +} + +// bindL1MessageQueue binds a generic wrapper to an already deployed contract. +func bindL1MessageQueue(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(L1MessageQueueABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// L1MessageQueueQueueTransactionIterator is returned from FilterQueueTransaction and is used to iterate over the raw logs and unpacked data for QueueTransaction events raised by the L1MessageQueue contract. +type L1MessageQueueQueueTransactionIterator struct { + Event *L1MessageQueueQueueTransaction // 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 *L1MessageQueueQueueTransactionIterator) 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(L1MessageQueueQueueTransaction) + 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(L1MessageQueueQueueTransaction) + 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 *L1MessageQueueQueueTransactionIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *L1MessageQueueQueueTransactionIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// L1MessageQueueQueueTransaction represents a QueueTransaction event raised by the L1MessageQueue contract. +type L1MessageQueueQueueTransaction struct { + Sender common.Address + Target common.Address + Value *big.Int + QueueIndex *big.Int + GasLimit *big.Int + Data []byte + Raw types.Log // Blockchain specific contextual infos +} + +// FilterQueueTransaction is a free log retrieval operation binding the contract event 0xbdcc7517f8fe3db6506dfd910942d0bbecaf3d6a506dadea65b0d988e75b9439. +// +// Solidity: event QueueTransaction(address indexed sender, address indexed target, uint256 value, uint256 queueIndex, uint256 gasLimit, bytes data) +func (_L1MessageQueue *L1MessageQueueFilterer) FilterQueueTransaction(opts *bind.FilterOpts, sender []common.Address, target []common.Address) (*L1MessageQueueQueueTransactionIterator, error) { + + var senderRule []interface{} + for _, senderItem := range sender { + senderRule = append(senderRule, senderItem) + } + var targetRule []interface{} + for _, targetItem := range target { + targetRule = append(targetRule, targetItem) + } + + logs, sub, err := _L1MessageQueue.contract.FilterLogs(opts, "QueueTransaction", senderRule, targetRule) + if err != nil { + return nil, err + } + return &L1MessageQueueQueueTransactionIterator{contract: _L1MessageQueue.contract, event: "QueueTransaction", logs: logs, sub: sub}, nil +} diff --git a/rollup/sync_service/bridge_client.go b/rollup/sync_service/bridge_client.go new file mode 100644 index 000000000..3c1b30d57 --- /dev/null +++ b/rollup/sync_service/bridge_client.go @@ -0,0 +1,129 @@ +package sync_service + +import ( + "context" + "errors" + "fmt" + "math/big" + + "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/rpc" +) + +// BridgeClient is a wrapper around EthClient that adds +// methods for conveniently collecting L1 messages. +type BridgeClient struct { + client EthClient + confirmations rpc.BlockNumber + l1MessageQueueAddress common.Address + filterer *L1MessageQueueFilterer +} + +func newBridgeClient(ctx context.Context, l1Client EthClient, l1ChainId uint64, confirmations rpc.BlockNumber, l1MessageQueueAddress common.Address) (*BridgeClient, error) { + if l1MessageQueueAddress == (common.Address{}) { + return nil, errors.New("must pass non-zero l1MessageQueueAddress to BridgeClient") + } + + // sanity check: compare chain IDs + got, err := l1Client.ChainID(ctx) + if err != nil { + return nil, fmt.Errorf("failed to query L1 chain ID, err = %w", err) + } + if got.Cmp(big.NewInt(0).SetUint64(l1ChainId)) != 0 { + return nil, fmt.Errorf("unexpected chain ID, expected = %v, got = %v", l1ChainId, got) + } + + filterer, err := NewL1MessageQueueFilterer(l1MessageQueueAddress, l1Client) + if err != nil { + return nil, fmt.Errorf("failed to initialize L1MessageQueueFilterer, err = %w", err) + } + + client := BridgeClient{ + client: l1Client, + confirmations: confirmations, + l1MessageQueueAddress: l1MessageQueueAddress, + filterer: filterer, + } + + return &client, nil +} + +// fetchMessagesInRange retrieves and parses all L1 messages between the +// provided from and to L1 block numbers (inclusive). +func (c *BridgeClient) fetchMessagesInRange(ctx context.Context, from, to uint64) ([]types.L1MessageTx, error) { + log.Trace("BridgeClient fetchMessagesInRange", "fromBlock", from, "toBlock", to) + + opts := bind.FilterOpts{ + Start: from, + End: &to, + Context: ctx, + } + it, err := c.filterer.FilterQueueTransaction(&opts, nil, nil) + if err != nil { + return nil, err + } + + var msgs []types.L1MessageTx + + for it.Next() { + event := it.Event + log.Trace("Received new L1 QueueTransaction event", "event", event) + + if !event.QueueIndex.IsUint64() || !event.GasLimit.IsUint64() { + return nil, fmt.Errorf("invalid QueueTransaction event: QueueIndex = %v, GasLimit = %v", event.QueueIndex, event.GasLimit) + } + + msgs = append(msgs, types.L1MessageTx{ + QueueIndex: event.QueueIndex.Uint64(), + Gas: event.GasLimit.Uint64(), + To: &event.Target, + Value: event.Value, + Data: event.Data, + Sender: event.Sender, + }) + } + + return msgs, 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 { + tag := big.NewInt(int64(c.confirmations)) + header, err := c.client.HeaderByNumber(ctx, tag) + if err != nil { + return 0, err + } + if !header.Number.IsInt64() { + return 0, fmt.Errorf("received unexpected block number in BridgeClient: %v", header.Number) + } + return header.Number.Uint64(), nil + } + + // confirmation based on latest block number + if c.confirmations == rpc.LatestBlockNumber { + number, err := c.client.BlockNumber(ctx) + if err != nil { + return 0, err + } + return number, nil + } + + // confirmation based on a certain number of blocks + if c.confirmations.Int64() >= 0 { + number, err := c.client.BlockNumber(ctx) + if err != nil { + return 0, err + } + confirmations := uint64(c.confirmations.Int64()) + if number >= confirmations { + return number - confirmations, nil + } + return 0, nil + } + + return 0, fmt.Errorf("unknown confirmation type: %v", c.confirmations) +} diff --git a/rollup/sync_service/sync_service.go b/rollup/sync_service/sync_service.go new file mode 100644 index 000000000..2720aec76 --- /dev/null +++ b/rollup/sync_service/sync_service.go @@ -0,0 +1,227 @@ +package sync_service + +import ( + "context" + "fmt" + "reflect" + "time" + + "github.com/scroll-tech/go-ethereum/core" + "github.com/scroll-tech/go-ethereum/core/rawdb" + "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/node" + "github.com/scroll-tech/go-ethereum/params" +) + +const ( + // DefaultFetchBlockRange is the number of blocks that we collect in a single eth_getLogs query. + DefaultFetchBlockRange = uint64(100) + + // DefaultPollInterval is the frequency at which we query for new L1 messages. + DefaultPollInterval = time.Second * 10 + + // LogProgressInterval is the frequency at which we log progress. + LogProgressInterval = time.Second * 10 + + // DbWriteThresholdBytes is the size of batched database writes in bytes. + DbWriteThresholdBytes = 10 * 1024 + + // DbWriteThresholdBlocks is the number of blocks scanned after which we write to the database + // even if we have not collected DbWriteThresholdBytes bytes of data yet. This way, if there is + // 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 +) + +// SyncService collects all L1 messages and stores them in a local database. +type SyncService struct { + ctx context.Context + cancel context.CancelFunc + client *BridgeClient + db ethdb.Database + msgCountFeed event.Feed + pollInterval time.Duration + latestProcessedBlock uint64 + scope event.SubscriptionScope +} + +func NewSyncService(ctx context.Context, genesisConfig *params.ChainConfig, nodeConfig *node.Config, db ethdb.Database, 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") + return nil, nil + } + + if genesisConfig.Scroll.L1Config == nil { + return nil, fmt.Errorf("missing L1 config in genesis") + } + + client, err := newBridgeClient(ctx, l1Client, genesisConfig.Scroll.L1Config.L1ChainId, nodeConfig.L1Confirmations, genesisConfig.Scroll.L1Config.L1MessageQueueAddress) + if err != nil { + return nil, fmt.Errorf("failed to initialize bridge client: %w", err) + } + + // assume deployment block has 0 messages + latestProcessedBlock := nodeConfig.L1DeploymentBlock + block := rawdb.ReadSyncedL1BlockNumber(db) + if block != nil { + // restart from latest synced block number + latestProcessedBlock = *block + } + + ctx, cancel := context.WithCancel(ctx) + + service := SyncService{ + ctx: ctx, + cancel: cancel, + client: client, + db: db, + pollInterval: DefaultPollInterval, + latestProcessedBlock: latestProcessedBlock, + } + + return &service, nil +} + +func (s *SyncService) Start() { + if s == nil { + return + } + + // wait for initial sync before starting node + log.Info("Starting L1 message sync service", "latestProcessedBlock", s.latestProcessedBlock) + + // block node startup during initial sync and print some helpful logs + latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx) + if err == nil && latestConfirmed > s.latestProcessedBlock+1000 { + log.Warn("Running initial sync of L1 messages before starting l2geth, this might take a while...") + s.fetchMessages() + log.Info("L1 message initial sync completed", "latestProcessedBlock", s.latestProcessedBlock) + } + + go func() { + t := time.NewTicker(s.pollInterval) + defer t.Stop() + + for { + // don't wait for ticker during startup + s.fetchMessages() + + select { + case <-s.ctx.Done(): + return + case <-t.C: + continue + } + } + }() +} + +func (s *SyncService) Stop() { + if s == nil { + return + } + + log.Info("Stopping sync service") + + // Unsubscribe all subscriptions registered + s.scope.Close() + + if s.cancel != nil { + s.cancel() + } +} + +// SubscribeNewL1MsgsEvent registers a subscription of NewL1MsgsEvent and +// starts sending event to the given channel. +func (s *SyncService) SubscribeNewL1MsgsEvent(ch chan<- core.NewL1MsgsEvent) event.Subscription { + return s.scope.Track(s.msgCountFeed.Subscribe(ch)) +} + +func (s *SyncService) fetchMessages() { + latestConfirmed, err := s.client.getLatestConfirmedBlockNumber(s.ctx) + if err != nil { + log.Warn("Failed to get latest confirmed block number", "err", err) + return + } + + log.Trace("Sync service fetchMessages", "latestProcessedBlock", s.latestProcessedBlock, "latestConfirmed", latestConfirmed) + + batchWriter := s.db.NewBatch() + numBlocksPendingDbWrite := uint64(0) + numMessagesPendingDbWrite := 0 + + // helper function to flush database writes cached in memory + flush := func(lastBlock uint64) { + // update sync progress + rawdb.WriteSyncedL1BlockNumber(batchWriter, lastBlock) + + // write batch in a single transaction + err := batchWriter.Write() + if err != nil { + // crash on database error, no risk of inconsistency here + log.Crit("Failed to write L1 messages to database", "err", err) + } + + batchWriter.Reset() + numBlocksPendingDbWrite = 0 + + if numMessagesPendingDbWrite > 0 { + s.msgCountFeed.Send(core.NewL1MsgsEvent{Count: numMessagesPendingDbWrite}) + numMessagesPendingDbWrite = 0 + } + + s.latestProcessedBlock = lastBlock + } + + // ticker for logging progress + t := time.NewTicker(LogProgressInterval) + numMsgsCollected := 0 + + // query in batches + for from := s.latestProcessedBlock + 1; from <= latestConfirmed; from += DefaultFetchBlockRange { + select { + case <-s.ctx.Done(): + // flush pending writes to database + if from > 0 { + flush(from - 1) + } + return + case <-t.C: + progress := 100 * float64(s.latestProcessedBlock) / float64(latestConfirmed) + log.Info("Syncing L1 messages", "processed", s.latestProcessedBlock, "confirmed", latestConfirmed, "collected", numMsgsCollected, "progress(%)", progress) + default: + } + + to := from + DefaultFetchBlockRange - 1 + if to > latestConfirmed { + to = latestConfirmed + } + + msgs, err := s.client.fetchMessagesInRange(s.ctx, from, to) + if err != nil { + // flush pending writes to database + if from > 0 { + flush(from - 1) + } + log.Warn("Failed to fetch L1 messages in range", "fromBlock", from, "toBlock", to, "err", err) + return + } + + if len(msgs) > 0 { + log.Debug("Received new L1 events", "fromBlock", from, "toBlock", to, "count", len(msgs)) + rawdb.WriteL1Messages(batchWriter, msgs) // collect messages in memory + numMsgsCollected += len(msgs) + } + + numBlocksPendingDbWrite += to - from + numMessagesPendingDbWrite += len(msgs) + + // flush new messages to database periodically + if to == latestConfirmed || batchWriter.ValueSize() >= DbWriteThresholdBytes || numBlocksPendingDbWrite >= DbWriteThresholdBlocks { + flush(to) + } + } +} diff --git a/rollup/sync_service/types.go b/rollup/sync_service/types.go new file mode 100644 index 000000000..dfb9d51d2 --- /dev/null +++ b/rollup/sync_service/types.go @@ -0,0 +1,19 @@ +package sync_service + +import ( + "context" + "math/big" + + "github.com/scroll-tech/go-ethereum" + "github.com/scroll-tech/go-ethereum/core/types" +) + +// We cannot use ethclient.Client directly as that would lead +// to circular dependency between eth, rollup, and ethclient. +type EthClient interface { + BlockNumber(ctx context.Context) (uint64, error) + ChainID(ctx context.Context) (*big.Int, error) + FilterLogs(ctx context.Context, q ethereum.FilterQuery) ([]types.Log, error) + HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error) + SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) +} From 4e0daeb3007c322a8a2e242716bd43c7ff4f6550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Fri, 9 Jun 2023 16:28:40 +0200 Subject: [PATCH 18/22] fix(trace): change l1Fee type from uint64 to *big.Int (#360) * fix: change l1Fee type from uint64 to *big.Int * use hexutil.Big --- core/types/l2trace.go | 8 ++++---- eth/tracers/api.go | 1 + eth/tracers/api_blocktrace.go | 6 +----- eth/tracers/api_blocktrace_test.go | 2 ++ eth/tracers/api_test.go | 5 +++++ params/version.go | 2 +- 6 files changed, 14 insertions(+), 10 deletions(-) diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 0235d47e2..88523aff3 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -44,10 +44,10 @@ type StorageTrace struct { // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value type ExecutionResult struct { - L1Fee uint64 `json:"l1Fee,omitempty"` - Gas uint64 `json:"gas"` - Failed bool `json:"failed"` - ReturnValue string `json:"returnValue"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + Gas uint64 `json:"gas"` + Failed bool `json:"failed"` + ReturnValue string `json:"returnValue"` // Sender's account state (before Tx) From *AccountWrapper `json:"from,omitempty"` // Receiver's account state (before Tx) diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 3b962e28b..a3705319e 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -930,6 +930,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex Failed: result.Failed(), ReturnValue: returnVal, StructLogs: vm.FormatLogs(tracer.StructLogs()), + L1Fee: (*hexutil.Big)(result.L1Fee), }, nil case Tracer: diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 0a9a1ab44..6b1068803 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -384,16 +384,12 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc } } - var l1Fee uint64 - if result.L1Fee != nil { - l1Fee = result.L1Fee.Uint64() - } env.executionResults[index] = &types.ExecutionResult{ From: sender, To: receiver, AccountCreated: createdAcc, AccountsAfter: after, - L1Fee: l1Fee, + L1Fee: (*hexutil.Big)(result.L1Fee), Gas: result.UsedGas, Failed: result.Failed(), ReturnValue: fmt.Sprintf("%x", returnVal), diff --git a/eth/tracers/api_blocktrace_test.go b/eth/tracers/api_blocktrace_test.go index c3c5c70eb..d4d8f02a3 100644 --- a/eth/tracers/api_blocktrace_test.go +++ b/eth/tracers/api_blocktrace_test.go @@ -164,10 +164,12 @@ func checkStructLogs(t *testing.T, expect []*txTraceResult, actual []*types.Exec assert.Equal(t, len(expect), len(actual)) for i, val := range expect { trace1, trace2 := val.Result.(*types.ExecutionResult), actual[i] + assert.Equal(t, trace1.L1Fee, trace2.L1Fee) assert.Equal(t, trace1.Failed, trace2.Failed) assert.Equal(t, trace1.Gas, trace2.Gas) assert.Equal(t, trace1.ReturnValue, trace2.ReturnValue) assert.Equal(t, len(trace1.StructLogs), len(trace2.StructLogs)) + // TODO: compare other fields for i, opcode := range trace1.StructLogs { data1, err := json.Marshal(opcode) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index fe9780590..c36ecd441 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -218,6 +218,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ + L1Fee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -235,6 +236,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ + L1Fee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -264,6 +266,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ + L1Fee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -281,6 +284,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ + L1Fee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -334,6 +338,7 @@ func TestTraceTransaction(t *testing.T) { t.Errorf("Failed to trace transaction %v", err) } if !reflect.DeepEqual(result, &types.ExecutionResult{ + L1Fee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", diff --git a/params/version.go b/params/version.go index f61723a51..cf6c36a0e 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 1 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) From f055f50f9d5605df661714677813c8c4779868de Mon Sep 17 00:00:00 2001 From: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:57:59 +0800 Subject: [PATCH 19/22] feat: update l1fee calculation (#351) --- accounts/abi/bind/backends/simulated.go | 8 +- cmd/evm/internal/t8ntool/execution.go | 10 +- core/state_prefetcher.go | 14 +- core/state_processor.go | 10 +- core/state_transition.go | 43 +++-- core/tx_pool.go | 2 +- core/tx_pool_test.go | 64 +++---- core/types/l2trace.go | 2 +- eth/state_accessor.go | 7 +- eth/tracers/api.go | 75 ++++++-- eth/tracers/api_blocktrace.go | 16 +- eth/tracers/api_blocktrace_test.go | 2 +- eth/tracers/api_test.go | 17 +- .../internal/tracetest/calltrace_test.go | 19 +- eth/tracers/tracers_test.go | 7 +- ethclient/gethclient/gethclient_test.go | 2 +- internal/ethapi/api.go | 33 +++- les/odr_test.go | 9 +- les/state_accessor.go | 7 +- light/odr_test.go | 5 +- params/config.go | 2 +- params/version.go | 4 +- rollup/fees/rollup_fee.go | 172 +++++++++--------- rollup/fees/rollup_fee_test.go | 4 +- tests/state_test_util.go | 11 +- 25 files changed, 344 insertions(+), 201 deletions(-) diff --git a/accounts/abi/bind/backends/simulated.go b/accounts/abi/bind/backends/simulated.go index 9f8e483fa..2f4a9216a 100644 --- a/accounts/abi/bind/backends/simulated.go +++ b/accounts/abi/bind/backends/simulated.go @@ -42,6 +42,7 @@ import ( "github.com/scroll-tech/go-ethereum/event" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -638,8 +639,13 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM // about the transaction and calling mechanisms. vmEnv := vm.NewEVM(evmContext, txContext, stateDB, b.config, vm.Config{NoBaseFee: true}) gasPool := new(core.GasPool).AddGas(math.MaxUint64) + signer := types.MakeSigner(b.blockchain.Config(), head.Number) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, head.BaseFee, b.blockchain.Config().ChainID, signer, stateDB) + if err != nil { + return nil, err + } - return core.NewStateTransition(vmEnv, msg, gasPool).TransitionDb() + return core.NewStateTransition(vmEnv, msg, gasPool, l1DataFee).TransitionDb() } // SendTransaction updates the pending block to include the given transaction. diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 2f0204a2b..e101b2c3b 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -166,8 +167,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, snapshot := statedb.Snapshot() evm := vm.NewEVM(vmContext, txContext, statedb, chainConfig, vmConfig) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + log.Info("rejected tx due to fees.CalculateL1DataFee", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err) + rejectedTxs = append(rejectedTxs, &rejectedTx{i, err.Error()}) + continue + } + // (ret []byte, usedGas uint64, failed bool, err error) - msgResult, err := core.ApplyMessage(evm, msg, gaspool) + msgResult, err := core.ApplyMessage(evm, msg, gaspool, l1DataFee) if err != nil { statedb.RevertToSnapshot(snapshot) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From(), "error", err) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 8ba89a953..47bf31c76 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -17,6 +17,7 @@ package core import ( + "math/big" "sync/atomic" "github.com/scroll-tech/go-ethereum/consensus" @@ -24,6 +25,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // statePrefetcher is a basic Prefetcher, which blindly executes a block on top @@ -68,7 +70,13 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c return // Also invalid block, bail out } statedb.Prepare(tx.Hash(), i) - if err := precacheTransaction(msg, p.config, gaspool, statedb, header, evm); err != nil { + + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return + } + + if err = precacheTransaction(msg, p.config, gaspool, statedb, header, evm, l1DataFee); err != nil { return // Ugh, something went horribly wrong, bail out } // If we're pre-byzantium, pre-load trie nodes for the intermediate root @@ -85,10 +93,10 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // precacheTransaction attempts to apply a transaction to the given state database // and uses the input parameters for its environment. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. -func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM) error { +func precacheTransaction(msg types.Message, config *params.ChainConfig, gaspool *GasPool, statedb *state.StateDB, header *types.Header, evm *vm.EVM, l1DataFee *big.Int) error { // Update the evm with the new transaction context. evm.Reset(NewEVMTxContext(msg), statedb) // Add addresses to access list if applicable - _, err := ApplyMessage(evm, msg, gaspool) + _, err := ApplyMessage(evm, msg, gaspool, l1DataFee) return err } diff --git a/core/state_processor.go b/core/state_processor.go index e6d0a538e..8011adf24 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -28,6 +28,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // StateProcessor is a basic Processor, which takes care of transitioning @@ -97,8 +98,13 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon txContext := NewEVMTxContext(msg) evm.Reset(txContext, statedb) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, err + } + // Apply the transaction to the current state (included in the env). - result, err := ApplyMessage(evm, msg, gp) + result, err := ApplyMessage(evm, msg, gp, l1DataFee) if err != nil { return nil, err } @@ -139,7 +145,7 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon receipt.BlockHash = blockHash receipt.BlockNumber = blockNumber receipt.TransactionIndex = uint(statedb.TxIndex()) - receipt.L1Fee = result.L1Fee + receipt.L1Fee = result.L1DataFee return receipt, err } diff --git a/core/state_transition.go b/core/state_transition.go index 6b13d6541..3aeb75c02 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -28,7 +28,6 @@ import ( "github.com/scroll-tech/go-ethereum/crypto/codehash" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" - "github.com/scroll-tech/go-ethereum/rollup/fees" ) var emptyKeccakCodeHash = codehash.EmptyKeccakCodeHash @@ -63,8 +62,7 @@ type StateTransition struct { state vm.StateDB evm *vm.EVM - // l1 rollup fee - l1Fee *big.Int + l1DataFee *big.Int } // Message represents a message sent to a contract. @@ -88,7 +86,7 @@ type Message interface { // ExecutionResult includes all output after executing given evm // message no matter the execution itself is successful or not. type ExecutionResult struct { - L1Fee *big.Int + L1DataFee *big.Int UsedGas uint64 // Total used gas but include the refunded gas Err error // Any error encountered during the execution(listed in core/vm/errors.go) ReturnData []byte // Returned data from evm(function result or data supplied with revert opcode) @@ -181,12 +179,7 @@ func toWordSize(size uint64) uint64 { } // NewStateTransition initialises and returns a new state transition object. -func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition { - l1Fee := new(big.Int) - if evm.ChainConfig().Scroll.FeeVaultEnabled() { - l1Fee, _ = fees.CalculateL1MsgFee(msg, evm.StateDB) - } - +func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool, l1DataFee *big.Int) *StateTransition { return &StateTransition{ gp: gp, evm: evm, @@ -197,7 +190,7 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition value: msg.Value(), data: msg.Data(), state: evm.StateDB, - l1Fee: l1Fee, + l1DataFee: l1DataFee, } } @@ -208,8 +201,8 @@ func NewStateTransition(evm *vm.EVM, msg Message, gp *GasPool) *StateTransition // the gas used (which includes gas refunds) and an error if it failed. An error always // indicates a core error meaning that the message would always fail for that particular // state and would never be accepted within a block. -func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) (*ExecutionResult, error) { - return NewStateTransition(evm, msg, gp).TransitionDb() +func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool, l1DataFee *big.Int) (*ExecutionResult, error) { + return NewStateTransition(evm, msg, gp, l1DataFee).TransitionDb() } // to returns the recipient of the message. @@ -225,9 +218,12 @@ func (st *StateTransition) buyGas() error { mgval = mgval.Mul(mgval, st.gasPrice) if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { - // always add l1fee, because all tx are L2-to-L1 ATM - log.Debug("Adding L1 fee", "l1_fee", st.l1Fee) - mgval = mgval.Add(mgval, st.l1Fee) + // should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee, + // but double check to make sure + if !st.msg.IsL1MessageTx() { + log.Debug("Adding L1DataFee", "l1DataFee", st.l1DataFee) + mgval = mgval.Add(mgval, st.l1DataFee) + } } balanceCheck := mgval @@ -236,8 +232,11 @@ func (st *StateTransition) buyGas() error { balanceCheck = balanceCheck.Mul(balanceCheck, st.gasFeeCap) balanceCheck.Add(balanceCheck, st.value) if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { - // always add l1fee, because all tx are L2-to-L1 ATM - balanceCheck.Add(balanceCheck, st.l1Fee) + // should be fine to add st.l1DataFee even without `L1MessageTx` check, since L1MessageTx will come with 0 l1DataFee, + // but double check to make sure + if !st.msg.IsL1MessageTx() { + balanceCheck.Add(balanceCheck, st.l1DataFee) + } } } if have, want := st.state.GetBalance(st.msg.From()), balanceCheck; have.Cmp(want) < 0 { @@ -391,7 +390,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { // no refunds for l1 messages if st.msg.IsL1MessageTx() { return &ExecutionResult{ - L1Fee: big.NewInt(0), + L1DataFee: big.NewInt(0), UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, @@ -416,17 +415,17 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.evm.ChainConfig().Scroll.FeeVaultEnabled() { // The L2 Fee is the same as the fee that is charged in the normal geth - // codepath. Add the L1 fee to the L2 fee for the total fee that is sent + // codepath. Add the L1DataFee to the L2 fee for the total fee that is sent // to the sequencer. l2Fee := new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip) - fee := new(big.Int).Add(st.l1Fee, l2Fee) + fee := new(big.Int).Add(st.l1DataFee, l2Fee) st.state.AddBalance(st.evm.FeeRecipient(), fee) } else { st.state.AddBalance(st.evm.FeeRecipient(), new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), effectiveTip)) } return &ExecutionResult{ - L1Fee: st.l1Fee, + L1DataFee: st.l1DataFee, UsedGas: st.gasUsed(), Err: vmerr, ReturnData: ret, diff --git a/core/tx_pool.go b/core/tx_pool.go index 810cb8edd..abd45c796 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -695,7 +695,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e if pool.chainconfig.Scroll.FeeVaultEnabled() { if err := fees.VerifyFee(pool.signer, tx, pool.currentState); err != nil { - log.Trace("Discarding insufficient l1fee transaction", "hash", hash, "err", err) + log.Trace("Discarding insufficient l1DataFee transaction", "hash", hash, "err", err) invalidTxMeter.Mark(1) return false, err } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 3a701cce2..5ccc304cc 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -43,32 +43,32 @@ var ( // sideeffects used during testing. testTxPoolConfig TxPoolConfig - // noL1feeConfig is a chain config without L1fee enabled. - noL1feeConfig *params.ChainConfig + // noL1DataFeeConfig is a chain config without L1DataFee enabled. + noL1DataFeeConfig *params.ChainConfig // eip1559Config is a chain config with EIP-1559 enabled at block 0. eip1559Config *params.ChainConfig - // eip1559NoL1feeConfig is a chain config with EIP-1559 enabled at block 0 but not enabling L1fee. - eip1559NoL1feeConfig *params.ChainConfig + // eip1559NoL1DataFeeConfig is a chain config with EIP-1559 enabled at block 0 but not enabling L1DataFee. + eip1559NoL1DataFeeConfig *params.ChainConfig ) func init() { testTxPoolConfig = DefaultTxPoolConfig testTxPoolConfig.Journal = "" - cpy0 := *params.TestNoL1feeChainConfig - noL1feeConfig = &cpy0 + cpy0 := *params.TestNoL1DataFeeChainConfig + noL1DataFeeConfig = &cpy0 cpy1 := *params.TestChainConfig eip1559Config = &cpy1 eip1559Config.BerlinBlock = common.Big0 eip1559Config.LondonBlock = common.Big0 - cpy2 := *params.TestNoL1feeChainConfig - eip1559NoL1feeConfig = &cpy2 - eip1559NoL1feeConfig.BerlinBlock = common.Big0 - eip1559NoL1feeConfig.LondonBlock = common.Big0 + cpy2 := *params.TestNoL1DataFeeChainConfig + eip1559NoL1DataFeeConfig = &cpy2 + eip1559NoL1DataFeeConfig.BerlinBlock = common.Big0 + eip1559NoL1DataFeeConfig.LondonBlock = common.Big0 } type testBlockChain struct { @@ -290,7 +290,7 @@ func testSetNonce(pool *TxPool, addr common.Address, nonce uint64) { func TestInvalidTransactions(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -327,7 +327,7 @@ func TestInvalidTransactions(t *testing.T) { func TestTransactionQueue(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx := transaction(0, 100, key) @@ -358,7 +358,7 @@ func TestTransactionQueue(t *testing.T) { func TestTransactionQueue2(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx1 := transaction(0, 100, key) @@ -384,7 +384,7 @@ func TestTransactionQueue2(t *testing.T) { func TestTransactionNegativeValue(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(-1), 100, big.NewInt(1), nil), types.HomesteadSigner{}, key) @@ -398,7 +398,7 @@ func TestTransactionNegativeValue(t *testing.T) { func TestTransactionTipAboveFeeCap(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() tx := dynamicFeeTx(0, 100, big.NewInt(1), big.NewInt(2), key) @@ -411,7 +411,7 @@ func TestTransactionTipAboveFeeCap(t *testing.T) { func TestTransactionVeryHighValues(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() veryBigNumber := big.NewInt(1) @@ -431,7 +431,7 @@ func TestTransactionVeryHighValues(t *testing.T) { func TestTransactionChainFork(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -460,7 +460,7 @@ func TestTransactionChainFork(t *testing.T) { func TestTransactionDoubleNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -511,7 +511,7 @@ func TestTransactionDoubleNonce(t *testing.T) { func TestTransactionMissingNonce(t *testing.T) { t.Parallel() - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -535,7 +535,7 @@ func TestTransactionNonceRecovery(t *testing.T) { t.Parallel() const n = 10 - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() addr := crypto.PubkeyToAddress(key.PublicKey) @@ -561,7 +561,7 @@ func TestTransactionDropping(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -779,7 +779,7 @@ func TestTransactionGapFilling(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -833,7 +833,7 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1114,7 +1114,7 @@ func TestTransactionPendingLimiting(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1203,7 +1203,7 @@ func TestTransactionAllowedTxSize(t *testing.T) { t.Parallel() // Create a test account and fund it - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -1463,7 +1463,7 @@ func TestTransactionPoolRepricingDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() // Keep track of transaction events to ensure all executables get announced @@ -1834,7 +1834,7 @@ func TestTransactionPoolStableUnderpricing(t *testing.T) { func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() pool.config.GlobalSlots = 2 @@ -1941,7 +1941,7 @@ func TestTransactionPoolUnderpricingDynamicFee(t *testing.T) { func TestDualHeapEviction(t *testing.T) { t.Parallel() - pool, _ := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, _ := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() pool.config.GlobalSlots = 10 @@ -2144,7 +2144,7 @@ func TestTransactionReplacementDynamicFee(t *testing.T) { t.Parallel() // Create the pool to test the pricing enforcement with - pool, key := setupTxPoolWithConfig(eip1559NoL1feeConfig) + pool, key := setupTxPoolWithConfig(eip1559NoL1DataFeeConfig) defer pool.Stop() testAddBalance(pool, crypto.PubkeyToAddress(key.PublicKey), big.NewInt(1000000000)) @@ -2443,7 +2443,7 @@ func BenchmarkPendingDemotion10000(b *testing.B) { benchmarkPendingDemotion(b, 1 func benchmarkPendingDemotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2468,7 +2468,7 @@ func BenchmarkFuturePromotion10000(b *testing.B) { benchmarkFuturePromotion(b, 1 func benchmarkFuturePromotion(b *testing.B, size int) { // Add a batch of transactions to a pool one by one - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) @@ -2496,7 +2496,7 @@ func BenchmarkPoolBatchLocalInsert10000(b *testing.B) { benchmarkPoolBatchInsert func benchmarkPoolBatchInsert(b *testing.B, size int, local bool) { // Generate a batch of transactions to enqueue into the pool - pool, key := setupTxPoolWithConfig(noL1feeConfig) + pool, key := setupTxPoolWithConfig(noL1DataFeeConfig) defer pool.Stop() account := crypto.PubkeyToAddress(key.PublicKey) diff --git a/core/types/l2trace.go b/core/types/l2trace.go index 88523aff3..42fd80df7 100644 --- a/core/types/l2trace.go +++ b/core/types/l2trace.go @@ -44,7 +44,7 @@ type StorageTrace struct { // while replaying a transaction in debug mode as well as transaction // execution status, the amount of gas used and the return value type ExecutionResult struct { - L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + L1DataFee *hexutil.Big `json:"l1DataFee,omitempty"` Gas uint64 `json:"gas"` Failed bool `json:"failed"` ReturnValue string `json:"returnValue"` diff --git a/eth/state_accessor.go b/eth/state_accessor.go index bac31fe7f..6c2d2f9bf 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -27,6 +27,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/log" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -191,7 +192,11 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, eth.blockchain.Config(), vm.Config{}) statedb.Prepare(tx.Hash(), idx) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, err + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/eth/tracers/api.go b/eth/tracers/api.go index a3705319e..708b82813 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -283,7 +283,16 @@ func (api *API) traceChain(ctx context.Context, start, end *types.Block, config TxIndex: i, TxHash: tx.Hash(), } - res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config) + + l1DataFee, err := fees.CalculateL1DataFee(tx, task.statedb) + if err != nil { + // though it's not a "tracing error", we still need to put it here + task.results[i] = &txTraceResult{Error: err.Error()} + log.Warn("CalculateL1DataFee failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) + break + } + + res, err := api.traceTx(localctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { task.results[i] = &txTraceResult{Error: err.Error()} log.Warn("Tracing failed", "hash", tx.Hash(), "block", task.block.NumberU64(), "err", err) @@ -534,7 +543,14 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config vmenv = vm.NewEVM(vmctx, txContext, statedb, chainConfig, vm.Config{}) ) statedb.Prepare(tx.Hash(), i) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + log.Warn("Tracing intermediate roots did not complete due to fees.CalculateL1DataFee", "txindex", i, "txhash", tx.Hash(), "err", err) + return nil, err + } + + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) // We intentionally don't return the error here: if we do, then the RPC server will not // return the roots. Most likely, the caller already knows that a certain transaction fails to @@ -608,7 +624,14 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac TxIndex: task.index, TxHash: txs[task.index].Hash(), } - res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config) + + l1DataFee, err := fees.CalculateL1DataFee(txs[task.index], task.statedb) + if err != nil { + // though it's not a "tracing error", we still need to put it here + results[task.index] = &txTraceResult{Error: err.Error()} + continue + } + res, err := api.traceTx(ctx, msg, txctx, blockCtx, task.statedb, config, l1DataFee) if err != nil { results[task.index] = &txTraceResult{Error: err.Error()} continue @@ -627,7 +650,12 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac msg, _ := tx.AsMessage(signer, block.BaseFee()) statedb.Prepare(tx.Hash(), i) vmenv := vm.NewEVM(blockCtx, core.NewEVMTxContext(msg), statedb, api.backend.ChainConfig(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + failed = err + break + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { failed = err break } @@ -740,7 +768,10 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block // Execute the transaction and flush any traces to disk vmenv := vm.NewEVM(vmctx, txContext, statedb, chainConfig, vmConf) statedb.Prepare(tx.Hash(), i) - _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err == nil { + _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) + } if writer != nil { writer.Flush() } @@ -777,7 +808,7 @@ func containsTx(block *types.Block, hash common.Hash) bool { // TraceTransaction returns the structured logs created during the execution of EVM // and returns them as a JSON object. func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config *TraceConfig) (interface{}, error) { - _, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) + tx, blockHash, blockNumber, index, err := api.backend.GetTransaction(ctx, hash) if err != nil { return nil, err } @@ -802,7 +833,11 @@ func (api *API) TraceTransaction(ctx context.Context, hash common.Hash, config * TxIndex: int(index), TxHash: hash, } - return api.traceTx(ctx, msg, txctx, vmctx, statedb, config) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, err + } + return api.traceTx(ctx, msg, txctx, vmctx, statedb, config, l1DataFee) } // TraceCall lets you trace a given eth_call. It collects the structured logs @@ -856,13 +891,20 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc Reexec: config.Reexec, } } - return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig) + + signer := types.MakeSigner(api.backend.ChainConfig(), block.Number()) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, block.BaseFee(), api.backend.ChainConfig().ChainID, signer, statedb) + if err != nil { + return nil, err + } + + return api.traceTx(ctx, msg, new(Context), vmctx, statedb, traceConfig, l1DataFee) } // traceTx configures a new tracer according to the provided configuration, and // executes the given message in the provided environment. The return value will // be tracer dependent. -func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig) (interface{}, error) { +func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Context, vmctx vm.BlockContext, statedb *state.StateDB, config *TraceConfig, l1DataFee *big.Int) (interface{}, error) { // Assemble the structured logger or the JavaScript tracer var ( tracer vm.EVMLogger @@ -899,20 +941,15 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex // Run the transaction with tracing enabled. vmenv := vm.NewEVM(vmctx, txContext, statedb, api.backend.ChainConfig(), vm.Config{Debug: true, Tracer: tracer, NoBaseFee: true}) - // If gasPrice is 0, make sure that the account has sufficient balance to cover `l1Fee`. - if api.backend.ChainConfig().Scroll.FeeVaultEnabled() && message.GasPrice().Cmp(big.NewInt(0)) == 0 { - l1Fee, err := fees.CalculateL1MsgFee(message, vmenv.StateDB) - if err != nil { - return nil, err - } - - statedb.AddBalance(message.From(), l1Fee) + // If gasPrice is 0, make sure that the account has sufficient balance to cover `l1DataFee`. + if message.GasPrice().Cmp(big.NewInt(0)) == 0 { + statedb.AddBalance(message.From(), l1DataFee) } // Call Prepare to clear out the statedb access list statedb.Prepare(txctx.TxHash, txctx.TxIndex) - result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas())) + result, err := core.ApplyMessage(vmenv, message, new(core.GasPool).AddGas(message.Gas()), l1DataFee) if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } @@ -930,7 +967,7 @@ func (api *API) traceTx(ctx context.Context, message core.Message, txctx *Contex Failed: result.Failed(), ReturnValue: returnVal, StructLogs: vm.FormatLogs(tracer.StructLogs()), - L1Fee: (*hexutil.Big)(result.L1Fee), + L1DataFee: (*hexutil.Big)(result.L1DataFee), }, nil case Tracer: diff --git a/eth/tracers/api_blocktrace.go b/eth/tracers/api_blocktrace.go index 6b1068803..89cab46ae 100644 --- a/eth/tracers/api_blocktrace.go +++ b/eth/tracers/api_blocktrace.go @@ -16,6 +16,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/log" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rollup/rcfg" "github.com/scroll-tech/go-ethereum/rollup/withdrawtrie" "github.com/scroll-tech/go-ethereum/rpc" @@ -182,7 +183,12 @@ func (api *API) getBlockTrace(block *types.Block, env *traceEnv) (*types.BlockTr msg, _ := tx.AsMessage(env.signer, block.BaseFee()) env.state.Prepare(tx.Hash(), i) vmenv := vm.NewEVM(env.blockCtx, core.NewEVMTxContext(msg), env.state, api.backend.ChainConfig(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, env.state) + if err != nil { + failed = err + break + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee); err != nil { failed = err break } @@ -258,7 +264,11 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc state.Prepare(txctx.TxHash, txctx.TxIndex) // Computes the new state by applying the given message. - result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, state) + if err != nil { + return fmt.Errorf("tracing failed: %w", err) + } + result, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) if err != nil { return fmt.Errorf("tracing failed: %w", err) } @@ -389,7 +399,7 @@ func (api *API) getTxResult(env *traceEnv, state *state.StateDB, index int, bloc To: receiver, AccountCreated: createdAcc, AccountsAfter: after, - L1Fee: (*hexutil.Big)(result.L1Fee), + L1DataFee: (*hexutil.Big)(result.L1DataFee), Gas: result.UsedGas, Failed: result.Failed(), ReturnValue: fmt.Sprintf("%x", returnVal), diff --git a/eth/tracers/api_blocktrace_test.go b/eth/tracers/api_blocktrace_test.go index d4d8f02a3..eea330ee2 100644 --- a/eth/tracers/api_blocktrace_test.go +++ b/eth/tracers/api_blocktrace_test.go @@ -164,7 +164,7 @@ func checkStructLogs(t *testing.T, expect []*txTraceResult, actual []*types.Exec assert.Equal(t, len(expect), len(actual)) for i, val := range expect { trace1, trace2 := val.Result.(*types.ExecutionResult), actual[i] - assert.Equal(t, trace1.L1Fee, trace2.L1Fee) + assert.Equal(t, trace1.L1DataFee, trace2.L1DataFee) assert.Equal(t, trace1.Failed, trace2.Failed) assert.Equal(t, trace1.Gas, trace2.Gas) assert.Equal(t, trace1.ReturnValue, trace2.ReturnValue) diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index c36ecd441..819914e8e 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -42,6 +42,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/internal/ethapi" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/rpc" ) @@ -172,7 +173,11 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block return msg, context, statedb, nil } vmenv := vm.NewEVM(context, txContext, statedb, b.chainConfig, vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, err + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) @@ -218,7 +223,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -236,7 +241,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -266,7 +271,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -284,7 +289,7 @@ func TestTraceCall(t *testing.T) { config: nil, expectErr: nil, expect: &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", @@ -338,7 +343,7 @@ func TestTraceTransaction(t *testing.T) { t.Errorf("Failed to trace transaction %v", err) } if !reflect.DeepEqual(result, &types.ExecutionResult{ - L1Fee: (*hexutil.Big)(big.NewInt(0)), + L1DataFee: (*hexutil.Big)(big.NewInt(0)), Gas: params.TxGas, Failed: false, ReturnValue: "", diff --git a/eth/tracers/internal/tracetest/calltrace_test.go b/eth/tracers/internal/tracetest/calltrace_test.go index 2be638874..7fc405b75 100644 --- a/eth/tracers/internal/tracetest/calltrace_test.go +++ b/eth/tracers/internal/tracetest/calltrace_test.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/eth/tracers" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/tests" // Force-load native and js pacakges, to trigger registration @@ -192,7 +193,11 @@ func testCallTracer(tracerName string, dirPath string, t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + t.Fatalf("failed to calculate l1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } @@ -303,7 +308,11 @@ func benchTracer(tracerName string, test *callTracerTest, b *testing.B) { } evm := vm.NewEVM(context, txContext, statedb, test.Genesis.Config, vm.Config{Debug: true, Tracer: tracer}) snap := statedb.Snapshot() - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + b.Fatalf("failed to calculate l1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { b.Fatalf("failed to execute transaction: %v", err) } @@ -372,7 +381,11 @@ func TestZeroValueToNotExitCall(t *testing.T) { if err != nil { t.Fatalf("failed to prepare transaction for tracing: %v", err) } - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + t.Fatalf("failed to calculate l1DataFee: %v", err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) if _, err = st.TransitionDb(); err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/eth/tracers/tracers_test.go b/eth/tracers/tracers_test.go index e8aece81c..7b64f78ee 100644 --- a/eth/tracers/tracers_test.go +++ b/eth/tracers/tracers_test.go @@ -28,6 +28,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/tests" ) @@ -111,7 +112,11 @@ func BenchmarkTransactionTrace(b *testing.B) { for i := 0; i < b.N; i++ { snap := statedb.Snapshot() - st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas())) + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + b.Fatal(err) + } + st := core.NewStateTransition(evm, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee) _, err = st.TransitionDb() if err != nil { b.Fatal(err) diff --git a/ethclient/gethclient/gethclient_test.go b/ethclient/gethclient/gethclient_test.go index 32a20597f..04a498d0f 100644 --- a/ethclient/gethclient/gethclient_test.go +++ b/ethclient/gethclient/gethclient_test.go @@ -334,7 +334,7 @@ func testCallContractNoGas(t *testing.T, client *rpc.Client) { } // this would fail with `insufficient funds for gas * price + value` - // before we started considering l1fee for 0 gas calls. + // before we started considering l1DataFee for 0 gas calls. if _, err := ec.CallContract(context.Background(), msg, big.NewInt(0), nil); err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 0f9fe5049..b277148a3 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -907,7 +907,7 @@ func newRPCBalance(balance *big.Int) **hexutil.Big { return &rpcBalance } -func CalculateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, config *params.ChainConfig) (*big.Int, error) { +func EstimateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64, config *params.ChainConfig) (*big.Int, error) { if !config.Scroll.FeeVaultEnabled() { return big.NewInt(0), nil } @@ -947,7 +947,8 @@ func CalculateL1MsgFee(ctx context.Context, b Backend, args TransactionArgs, blo evm.Cancel() }() - return fees.CalculateL1MsgFee(msg, evm.StateDB) + signer := types.MakeSigner(config, header.Number) + return fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, evm.StateDB) } func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride, timeout time.Duration, globalGasCap uint64) (*core.ExecutionResult, error) { @@ -990,7 +991,14 @@ func DoCall(ctx context.Context, b Backend, args TransactionArgs, blockNrOrHash // Execute the message. gp := new(core.GasPool).AddGas(math.MaxUint64) - result, err := core.ApplyMessage(evm, msg, gp) + + signer := types.MakeSigner(b.ChainConfig(), header.Number) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, state) + if err != nil { + return nil, err + } + + result, err := core.ApplyMessage(evm, msg, gp, l1DataFee) if err := vmError(); err != nil { return nil, err } @@ -1043,7 +1051,7 @@ func (e *revertError) ErrorData() interface{} { // useful to execute and retrieve values. func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, blockNrOrHash rpc.BlockNumberOrHash, overrides *StateOverride) (hexutil.Bytes, error) { // If gasPrice is 0 and no state override is set, make sure - // that the account has sufficient balance to cover `l1Fee`. + // that the account has sufficient balance to cover `l1DataFee`. isGasPriceZero := args.GasPrice == nil || args.GasPrice.ToInt().Cmp(big.NewInt(0)) == 0 if overrides == nil { @@ -1052,13 +1060,13 @@ func (s *PublicBlockChainAPI) Call(ctx context.Context, args TransactionArgs, bl _, isOverrideSet := (*overrides)[args.from()] if isGasPriceZero && !isOverrideSet { - l1Fee, err := CalculateL1MsgFee(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), s.b.ChainConfig()) + l1DataFee, err := EstimateL1MsgFee(ctx, s.b, args, blockNrOrHash, overrides, s.b.RPCEVMTimeout(), s.b.RPCGasCap(), s.b.ChainConfig()) if err != nil { return nil, err } (*overrides)[args.from()] = OverrideAccount{ - BalanceAdd: newRPCBalance(l1Fee), + BalanceAdd: newRPCBalance(l1DataFee), } } @@ -1127,14 +1135,14 @@ func DoEstimateGas(ctx context.Context, b Backend, args TransactionArgs, blockNr } // account for l1 fee - l1Fee, err := CalculateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig()) + l1DataFee, err := EstimateL1MsgFee(ctx, b, args, blockNrOrHash, nil, 0, gasCap, b.ChainConfig()) if err != nil { return 0, err } - if l1Fee.Cmp(available) >= 0 { + if l1DataFee.Cmp(available) >= 0 { return 0, errors.New("insufficient funds for l1 fee") } - available.Sub(available, l1Fee) + available.Sub(available, l1DataFee) allowance := new(big.Int).Div(available, feeCap) @@ -1501,7 +1509,12 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if err != nil { return nil, 0, nil, err } - res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas())) + signer := types.MakeSigner(b.ChainConfig(), header.Number) + l1DataFee, err := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, b.ChainConfig().ChainID, signer, statedb) + if err != nil { + return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) + } + res, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), l1DataFee) if err != nil { return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", args.toTransaction().Hash(), err) } diff --git a/les/odr_test.go b/les/odr_test.go index dd749bac8..2f7af21d6 100644 --- a/les/odr_test.go +++ b/les/odr_test.go @@ -37,6 +37,7 @@ import ( "github.com/scroll-tech/go-ethereum/light" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) type odrTestFn func(ctx context.Context, db ethdb.Database, config *params.ChainConfig, bc *core.BlockChain, lc *light.LightChain, bhash common.Hash) []byte @@ -143,7 +144,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai //vmenv := core.NewEnv(statedb, config, bc, msg, header, vm.Config{}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, statedb) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) res = append(res, result.Return()...) } } else { @@ -155,7 +158,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, config *params.Chai txContext := core.NewEVMTxContext(msg) vmenv := vm.NewEVM(context, txContext, state, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, state) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) if state.Error() == nil { res = append(res, result.Return()...) } diff --git a/les/state_accessor.go b/les/state_accessor.go index 8b2319a11..93ed93e53 100644 --- a/les/state_accessor.go +++ b/les/state_accessor.go @@ -26,6 +26,7 @@ import ( "github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/vm" "github.com/scroll-tech/go-ethereum/light" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // stateAtBlock retrieves the state database associated with a certain block. @@ -64,7 +65,11 @@ func (leth *LightEthereum) stateAtTransaction(ctx context.Context, block *types. } // Not yet the searched for transaction, execute on top of the current state vmenv := vm.NewEVM(context, txContext, statedb, leth.blockchain.Config(), vm.Config{}) - if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas())); err != nil { + l1DataFee, err := fees.CalculateL1DataFee(tx, statedb) + if err != nil { + return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) + } + if _, err = core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(tx.Gas()), l1DataFee); err != nil { return nil, vm.BlockContext{}, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } // Ensure any modifications are committed to the state diff --git a/light/odr_test.go b/light/odr_test.go index 8145af607..4d359312b 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -36,6 +36,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" "github.com/scroll-tech/go-ethereum/trie" ) @@ -199,7 +200,9 @@ func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain context := core.NewEVMBlockContext(header, chain, nil) vmenv := vm.NewEVM(context, txContext, st, config, vm.Config{NoBaseFee: true}) gp := new(core.GasPool).AddGas(math.MaxUint64) - result, _ := core.ApplyMessage(vmenv, msg, gp) + signer := types.MakeSigner(config, header.Number) + l1DataFee, _ := fees.EstimateL1DataFeeForMessage(msg, header.BaseFee, config.ChainID, signer, st) + result, _ := core.ApplyMessage(vmenv, msg, gp, l1DataFee) res = append(res, result.Return()...) if st.Error() != nil { return res, st.Error() diff --git a/params/config.go b/params/config.go index e84e7361f..c0522c54f 100644 --- a/params/config.go +++ b/params/config.go @@ -340,7 +340,7 @@ var ( }} TestRules = TestChainConfig.Rules(new(big.Int)) - TestNoL1feeChainConfig = &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), nil, nil, new(EthashConfig), nil, + TestNoL1DataFeeChainConfig = &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), nil, nil, new(EthashConfig), nil, ScrollConfig{ UseZktrie: false, FeeVaultAddress: nil, diff --git a/params/version.go b/params/version.go index cf6c36a0e..8f6ff5ba4 100644 --- a/params/version.go +++ b/params/version.go @@ -23,8 +23,8 @@ import ( const ( VersionMajor = 4 // Major version component of the current release - VersionMinor = 1 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionMinor = 2 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) diff --git a/rollup/fees/rollup_fee.go b/rollup/fees/rollup_fee.go index dd2a726f0..81e427d63 100644 --- a/rollup/fees/rollup_fee.go +++ b/rollup/fees/rollup_fee.go @@ -8,24 +8,17 @@ import ( "github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/core/types" + "github.com/scroll-tech/go-ethereum/crypto" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rollup/rcfg" ) var ( - // errTransactionSigned represents the error case of passing in a signed - // transaction to the L1 fee calculation routine. The signature is accounted - // for externally - errTransactionSigned = errors.New("transaction is signed") - // txExtraDataBytes is the number of bytes that we commit to L1 in addition - // to the RLP-encoded unsigned transaction. Note that these are all assumed + // to the RLP-encoded signed transaction. Note that these are all assumed // to be non-zero. // - tx length prefix: 4 bytes - // - sig.r: 32 bytes + 1 byte rlp prefix - // - sig.s: 32 bytes + 1 byte rlp prefix - // - sig.v: 3 bytes + 1 byte rlp prefix - txExtraDataBytes = uint64(74) + txExtraDataBytes = uint64(4) ) // Message represents the interface of a message. @@ -36,9 +29,13 @@ type Message interface { To() *common.Address GasPrice() *big.Int Gas() uint64 + GasFeeCap() *big.Int + GasTipCap() *big.Int Value() *big.Int Nonce() uint64 Data() []byte + AccessList() types.AccessList + IsL1MessageTx() bool } // StateDB represents the StateDB interface @@ -48,59 +45,87 @@ type StateDB interface { GetBalance(addr common.Address) *big.Int } -// CalculateL1MsgFee computes the L1 portion of the fee given -// a Message and a StateDB -// Reference: https://github.com/ethereum-optimism/optimism/blob/develop/l2geth/rollup/fees/rollup_fee.go -func CalculateL1MsgFee(msg Message, state StateDB) (*big.Int, error) { - tx := asTransaction(msg) +func EstimateL1DataFeeForMessage(msg Message, baseFee, chainID *big.Int, signer types.Signer, state StateDB) (*big.Int, error) { + if msg.IsL1MessageTx() { + return big.NewInt(0), nil + } + + unsigned := asUnsignedTx(msg, baseFee, chainID) + // with v=1 + tx, err := unsigned.WithSignature(signer, append(bytes.Repeat([]byte{0xff}, crypto.SignatureLength-1), 0x01)) + if err != nil { + return nil, err + } + raw, err := rlpEncode(tx) if err != nil { return nil, err } l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar) - return l1Fee, nil -} - -// asTransaction turns a Message into a types.Transaction -func asTransaction(msg Message) *types.Transaction { - if msg.To() == nil { - return types.NewContractCreation( - msg.Nonce(), - msg.Value(), - msg.Gas(), - msg.GasPrice(), - msg.Data(), - ) + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + return l1DataFee, nil +} + +// asUnsignedTx turns a Message into a types.Transaction +func asUnsignedTx(msg Message, baseFee, chainID *big.Int) *types.Transaction { + if baseFee == nil { + if msg.AccessList() == nil { + return asUnsignedLegacyTx(msg) + } + + return asUnsignedAccessListTx(msg, chainID) } - return types.NewTransaction( - msg.Nonce(), - *msg.To(), - msg.Value(), - msg.Gas(), - msg.GasPrice(), - msg.Data(), - ) + + return asUnsignedDynamicTx(msg, chainID) +} + +func asUnsignedLegacyTx(msg Message) *types.Transaction { + return types.NewTx(&types.LegacyTx{ + Nonce: msg.Nonce(), + To: msg.To(), + Value: msg.Value(), + Gas: msg.Gas(), + GasPrice: msg.GasPrice(), + Data: msg.Data(), + }) +} + +func asUnsignedAccessListTx(msg Message, chainID *big.Int) *types.Transaction { + return types.NewTx(&types.AccessListTx{ + Nonce: msg.Nonce(), + To: msg.To(), + Value: msg.Value(), + Gas: msg.Gas(), + GasPrice: msg.GasPrice(), + Data: msg.Data(), + AccessList: msg.AccessList(), + ChainID: chainID, + }) +} + +func asUnsignedDynamicTx(msg Message, chainID *big.Int) *types.Transaction { + return types.NewTx(&types.DynamicFeeTx{ + Nonce: msg.Nonce(), + To: msg.To(), + Value: msg.Value(), + Gas: msg.Gas(), + GasFeeCap: msg.GasFeeCap(), + GasTipCap: msg.GasTipCap(), + Data: msg.Data(), + AccessList: msg.AccessList(), + ChainID: chainID, + }) } // rlpEncode RLP encodes the transaction into bytes -// When a signature is not included, set pad to true to -// fill in a dummy signature full on non 0 bytes func rlpEncode(tx *types.Transaction) ([]byte, error) { raw := new(bytes.Buffer) if err := tx.EncodeRLP(raw); err != nil { return nil, err } - r, v, s := tx.RawSignatureValues() - if r.Cmp(common.Big0) != 0 || v.Cmp(common.Big0) != 0 || s.Cmp(common.Big0) != 0 { - return nil, errTransactionSigned - } - - // Slice off the 0 bytes representing the signature - b := raw.Bytes() - return b[:len(b)-3], nil + return raw.Bytes(), nil } func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int, *big.Int) { @@ -110,11 +135,11 @@ func readGPOStorageSlots(addr common.Address, state StateDB) (*big.Int, *big.Int return l1BaseFee.Big(), overhead.Big(), scalar.Big() } -// CalculateL1Fee computes the L1 fee -func CalculateL1Fee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int { +// calculateEncodedL1DataFee computes the L1 fee for an RLP-encoded tx +func calculateEncodedL1DataFee(data []byte, overhead, l1GasPrice *big.Int, scalar *big.Int) *big.Int { l1GasUsed := CalculateL1GasUsed(data, overhead) - l1Fee := new(big.Int).Mul(l1GasUsed, l1GasPrice) - return mulAndScale(l1Fee, scalar, rcfg.Precision) + l1DataFee := new(big.Int).Mul(l1GasUsed, l1GasPrice) + return mulAndScale(l1DataFee, scalar, rcfg.Precision) } // CalculateL1GasUsed computes the L1 gas used based on the calldata and @@ -150,41 +175,24 @@ func mulAndScale(x *big.Int, y *big.Int, precision *big.Int) *big.Int { return new(big.Int).Quo(z, precision) } -// copyTransaction copies the transaction, removing the signature -func copyTransaction(tx *types.Transaction) *types.Transaction { - if tx.To() == nil { - return types.NewContractCreation( - tx.Nonce(), - tx.Value(), - tx.Gas(), - tx.GasPrice(), - tx.Data(), - ) +func CalculateL1DataFee(tx *types.Transaction, state StateDB) (*big.Int, error) { + if tx.IsL1MessageTx() { + return big.NewInt(0), nil } - return types.NewTransaction( - tx.Nonce(), - *tx.To(), - tx.Value(), - tx.Gas(), - tx.GasPrice(), - tx.Data(), - ) -} - -func CalculateFees(tx *types.Transaction, state StateDB) (*big.Int, *big.Int, *big.Int, error) { - unsigned := copyTransaction(tx) - raw, err := rlpEncode(unsigned) + + raw, err := rlpEncode(tx) if err != nil { - return nil, nil, nil, err + return nil, err } l1BaseFee, overhead, scalar := readGPOStorageSlots(rcfg.L1GasPriceOracleAddress, state) - l1Fee := CalculateL1Fee(raw, overhead, l1BaseFee, scalar) + l1DataFee := calculateEncodedL1DataFee(raw, overhead, l1BaseFee, scalar) + return l1DataFee, nil +} +func calculateL2Fee(tx *types.Transaction) *big.Int { l2GasLimit := new(big.Int).SetUint64(tx.Gas()) - l2Fee := new(big.Int).Mul(tx.GasPrice(), l2GasLimit) - fee := new(big.Int).Add(l1Fee, l2Fee) - return l1Fee, l2Fee, fee, nil + return new(big.Int).Mul(tx.GasPrice(), l2GasLimit) } func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error { @@ -194,8 +202,8 @@ func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error } balance := state.GetBalance(from) - - l1Fee, l2Fee, _, err := CalculateFees(tx, state) + l2Fee := calculateL2Fee(tx) + l1DataFee, err := CalculateL1DataFee(tx, state) if err != nil { return fmt.Errorf("invalid transaction: %w", err) } @@ -206,7 +214,7 @@ func VerifyFee(signer types.Signer, tx *types.Transaction, state StateDB) error return errors.New("invalid transaction: insufficient funds for gas * price + value") } - cost = cost.Add(cost, l1Fee) + cost = cost.Add(cost, l1DataFee) if balance.Cmp(cost) < 0 { return errors.New("invalid transaction: insufficient funds for l1fee + gas * price + value") } diff --git a/rollup/fees/rollup_fee_test.go b/rollup/fees/rollup_fee_test.go index d1bb2e9e7..54b77eeb1 100644 --- a/rollup/fees/rollup_fee_test.go +++ b/rollup/fees/rollup_fee_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -func TestCalculateL1Fee(t *testing.T) { +func TestCalculateEncodedL1DataFee(t *testing.T) { l1BaseFee := new(big.Int).SetUint64(15000000) data := []byte{0, 10, 1, 0} @@ -15,6 +15,6 @@ func TestCalculateL1Fee(t *testing.T) { scalar := new(big.Int).SetUint64(10) expected := new(big.Int).SetUint64(184) // 184.2 - actual := CalculateL1Fee(data, overhead, l1BaseFee, scalar) + actual := calculateEncodedL1DataFee(data, overhead, l1BaseFee, scalar) assert.Equal(t, expected, actual) } diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 1c8ab2c99..14457613f 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -39,6 +39,7 @@ import ( "github.com/scroll-tech/go-ethereum/ethdb" "github.com/scroll-tech/go-ethereum/params" "github.com/scroll-tech/go-ethereum/rlp" + "github.com/scroll-tech/go-ethereum/rollup/fees" ) // StateTest checks transaction processing without block context. @@ -201,9 +202,9 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh return nil, nil, common.Hash{}, err } + var ttx types.Transaction // Try to recover tx with current signer if len(post.TxBytes) != 0 { - var ttx types.Transaction err := ttx.UnmarshalBinary(post.TxBytes) if err != nil { return nil, nil, common.Hash{}, err @@ -225,7 +226,13 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh snapshot := statedb.Snapshot() gaspool := new(core.GasPool) gaspool.AddGas(block.GasLimit()) - if _, err := core.ApplyMessage(evm, msg, gaspool); err != nil { + + l1DataFee, err := fees.CalculateL1DataFee(&ttx, statedb) + if err != nil { + return nil, nil, common.Hash{}, err + } + + if _, err = core.ApplyMessage(evm, msg, gaspool, l1DataFee); err != nil { statedb.RevertToSnapshot(snapshot) } From b5e8d12c062fa1afe0ba36228e6e6c01560d7020 Mon Sep 17 00:00:00 2001 From: Richord Date: Wed, 14 Jun 2023 13:11:31 -0700 Subject: [PATCH 20/22] feat: return keccak(chainId || height) for BLOCKHASH opcode (#359) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * changed blockhash behavior * added patch version * Update core/vm/runtime/runtime_test.go Co-authored-by: Péter Garamvölgyi * Update core/vm/runtime/runtime_test.go Co-authored-by: Péter Garamvölgyi * lint fix * changed wording * used interpreter.hasher and padded chainId to uint64 * used interpreter.hasher and padded chainId to uint64 * undo changes * bump version --------- Co-authored-by: Péter Garamvölgyi Co-authored-by: Péter Garamvölgyi --- core/vm/instructions.go | 23 ++++++++++++++++++++--- core/vm/runtime/runtime_test.go | 14 +++++++++----- params/version.go | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index d05307427..f43ab576c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,8 @@ package vm import ( + "encoding/binary" + "github.com/holiman/uint256" "golang.org/x/crypto/sha3" @@ -440,13 +442,28 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( } var upper, lower uint64 upper = interpreter.evm.Context.BlockNumber.Uint64() - if upper < 2 { + if upper < 257 { lower = 0 } else { - lower = upper - 1 + lower = upper - 256 } if num64 >= lower && num64 < upper { - num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) + chainId := interpreter.evm.ChainConfig().ChainID + chainIdBuf := make([]byte, 8) + binary.BigEndian.PutUint64(chainIdBuf, chainId.Uint64()) + num64Buf := make([]byte, 8) + binary.BigEndian.PutUint64(num64Buf, num64) + + if interpreter.hasher == nil { + interpreter.hasher = sha3.NewLegacyKeccak256().(keccakState) + } else { + interpreter.hasher.Reset() + } + interpreter.hasher.Write(chainIdBuf) + interpreter.hasher.Write(num64Buf) + interpreter.hasher.Read(interpreter.hasherBuf[:]) + + num.SetBytes(interpreter.hasherBuf[:]) } else { num.Clear() } diff --git a/core/vm/runtime/runtime_test.go b/core/vm/runtime/runtime_test.go index 6892e65d9..46f94a219 100644 --- a/core/vm/runtime/runtime_test.go +++ b/core/vm/runtime/runtime_test.go @@ -334,11 +334,15 @@ func TestBlockhash(t *testing.T) { if zero.BitLen() != 0 { t.Fatalf("expected zeroes, got %x", ret[0:32]) } - if first.Uint64() != 999 { - t.Fatalf("second block should be 999, got %d (%x)", first, ret[32:64]) - } - if last.Uint64() != 0 { - t.Fatalf("last block should be 0, got %d (%x)", last, ret[64:96]) + firstExpectedHash := new(big.Int) + firstExpectedHash.SetString("13215081625009140218242111988507489764601005198286886925088730931502473149599", 10) + if first.Uint64() != firstExpectedHash.Uint64() { + t.Fatalf("first hash should be 13215081625009140218242111988507489764601005198286886925088730931502473149599, got %d (%x)", first, ret[32:64]) + } + lastExpectedHash := new(big.Int) + lastExpectedHash.SetString("2851160567348483005169712516804956111111231427377973738952179767509712807467", 10) + if last.Uint64() != lastExpectedHash.Uint64() { + t.Fatalf("last hash should be 2851160567348483005169712516804956111111231427377973738952179767509712807467, got %d (%x)", last, ret[64:96]) } if exp, got := 0, chain.counter; exp != got { t.Errorf("suboptimal; too much chain iteration, expected %d, got %d", exp, got) diff --git a/params/version.go b/params/version.go index 8f6ff5ba4..b765b8e71 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release + VersionPatch = 1 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string ) From 43be9a64508a8ec7afa208ffef764c4604f0c250 Mon Sep 17 00:00:00 2001 From: maskpp Date: Mon, 19 Jun 2023 11:32:20 +0800 Subject: [PATCH 21/22] feat(abigen): Add flag let be able to add user tmpl file. (#362) * Add user tmpl file. * Update accounts/abi/bind/template.go Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> * Update cmd/abigen/main.go Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> * Update cmd/abigen/main.go Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> * Update cmd/abigen/main.go Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> * fix bug --------- Co-authored-by: HAOYUatHZ <37070449+HAOYUatHZ@users.noreply.github.com> --- accounts/abi/bind/template.go | 7 +++++++ cmd/abigen/main.go | 14 ++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 8828a55c7..c96f205a3 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -79,6 +79,13 @@ var tmplSource = map[Lang]string{ LangJava: tmplSourceJava, } +// SetTmplSource supports this func in order to set special template file. +func SetTmplSource(lang Lang, source string) { + if _, ok := tmplSource[lang]; ok { + tmplSource[lang] = source + } +} + // tmplSourceGo is the Go source template that the generated Go contract binding // is based on. const tmplSourceGo = ` diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 0c6926f5f..eb207098c 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -103,6 +103,10 @@ var ( Name: "contract", Usage: "Name of the contract to generate the bindings for", } + tmplFlag = cli.StringFlag{ + Name: "tmpl", + Usage: "Template file if a user wants to customize", + } ) func init() { @@ -122,6 +126,7 @@ func init() { langFlag, aliasFlag, contractFlag, + tmplFlag, } app.Action = utils.MigrateFlags(abigen) cli.CommandHelpTemplate = flags.OriginCommandHelpTemplate @@ -265,6 +270,15 @@ func abigen(c *cli.Context) error { aliases[match[1]] = match[2] } } + // Set customize template file. + if c.GlobalIsSet(tmplFlag.Name) { + tmplFile := c.GlobalString(tmplFlag.Name) + data, err := os.ReadFile(tmplFile) + if err != nil { + utils.Fatalf("Failed to read template file: %v", err) + } + bind.SetTmplSource(lang, string(data)) + } // Generate the contract binding code, err := bind.Bind(types, abis, bins, sigs, c.GlobalString(pkgFlag.Name), lang, libs, aliases) if err != nil { From b14a4024cd2359e17622801acf2d41dfc348cc05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20Garamv=C3=B6lgyi?= Date: Mon, 19 Jun 2023 16:44:04 +0200 Subject: [PATCH 22/22] fix: improve L1Message RPC encoding (#368) correctly encode L1 messages in RPC response --- eth/api.go | 28 ++++++++++++++++++++++++++-- params/version.go | 2 +- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/eth/api.go b/eth/api.go index 9c7779c01..87fd6bc0d 100644 --- a/eth/api.go +++ b/eth/api.go @@ -613,6 +613,17 @@ type ScrollAPI struct { eth *Ethereum } +// l1MessageTxRPC is the RPC-layer representation of an L1 message. +type l1MessageTxRPC struct { + QueueIndex uint64 `json:"queueIndex"` + Gas uint64 `json:"gas"` + To *common.Address `json:"to"` + Value *hexutil.Big `json:"value"` + Data hexutil.Bytes `json:"data"` + Sender common.Address `json:"sender"` + Hash common.Hash `json:"hash"` +} + // NewScrollAPI creates a new RPC service to query the L1 message database. func NewScrollAPI(eth *Ethereum) *ScrollAPI { return &ScrollAPI{eth: eth} @@ -624,8 +635,21 @@ func (api *ScrollAPI) GetL1SyncHeight(ctx context.Context) (height *uint64, err } // GetL1MessageByIndex queries an L1 message by its index in the local database. -func (api *ScrollAPI) GetL1MessageByIndex(ctx context.Context, queueIndex uint64) (height *types.L1MessageTx, err error) { - return rawdb.ReadL1Message(api.eth.ChainDb(), queueIndex), nil +func (api *ScrollAPI) GetL1MessageByIndex(ctx context.Context, queueIndex uint64) (height *l1MessageTxRPC, err error) { + msg := rawdb.ReadL1Message(api.eth.ChainDb(), queueIndex) + if msg == nil { + return nil, nil + } + rpcMsg := l1MessageTxRPC{ + QueueIndex: msg.QueueIndex, + Gas: msg.Gas, + To: msg.To, + Value: (*hexutil.Big)(msg.Value), + Data: msg.Data, + Sender: msg.Sender, + Hash: types.NewTx(msg).Hash(), + } + return &rpcMsg, nil } // GetFirstQueueIndexNotInL2Block returns the first L1 message queue index that is diff --git a/params/version.go b/params/version.go index b765b8e71..044c305da 100644 --- a/params/version.go +++ b/params/version.go @@ -24,7 +24,7 @@ import ( const ( VersionMajor = 4 // Major version component of the current release VersionMinor = 2 // Minor version component of the current release - VersionPatch = 1 // Patch version component of the current release + VersionPatch = 2 // Patch version component of the current release VersionMeta = "sepolia" // Version metadata to append to the version string )