From f7a3a10cf54ff882df9402f0e79f12841d051cd1 Mon Sep 17 00:00:00 2001 From: mralj Date: Mon, 7 Oct 2024 13:18:17 +0200 Subject: [PATCH 1/3] removed GitHub workflows so that I can work with this branhc --- .github/workflows/go.yml | 0 .github/workflows/pages.yaml | 36 ------------------------------------ 2 files changed, 36 deletions(-) delete mode 100644 .github/workflows/go.yml delete mode 100644 .github/workflows/pages.yaml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/.github/workflows/pages.yaml b/.github/workflows/pages.yaml deleted file mode 100644 index 3c45b5d3390e..000000000000 --- a/.github/workflows/pages.yaml +++ /dev/null @@ -1,36 +0,0 @@ -name: Build and publish forkdiff github-pages -permissions: - contents: write -on: - push: - branches: - - optimism -jobs: - deploy: - concurrency: ci-${{ github.ref }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 1000 # make sure to fetch the old commit we diff against - - - name: Build forkdiff - uses: "docker://protolambda/forkdiff:0.1.0" - with: - args: -repo=/github/workspace -fork=/github/workspace/fork.yaml -out=/github/workspace/index.html - - - name: Build pages - run: | - mkdir -p tmp/pages - mv index.html tmp/pages/index.html - touch tmp/pages/.nojekyll - if [ "$GITHUB_REPOSITORY" == "ethereum-optimism/op-geth" ]; then - echo "op-geth.optimism.io" > tmp/pages/CNAME - fi; - - - name: Deploy - uses: JamesIves/github-pages-deploy-action@v4 - with: - folder: tmp/pages - clean: true From 7f39c7ec2be8a373449ec6b8236bde70795ddffb Mon Sep 17 00:00:00 2001 From: mralj Date: Fri, 11 Oct 2024 12:47:58 +0200 Subject: [PATCH 2/3] Squashed commit of the following: commit 237b78f4f11f142669cf58b30b5a1c7150b2bc11 Author: mralj Date: Fri Oct 11 12:46:51 2024 +0200 removed unnecessary call to vm.SetVmL1RpcClient commit d4cd646fef9c8d9a1a0d94f5361608efe7c6aaa3 Author: mralj Date: Fri Oct 11 12:16:24 2024 +0200 rollup precompile config is glob. variable I decided to implement it this way after trying to integrate code with Arbitrum and having a better understanding of the calls that are made to the NewEvm This approach makes it easier to both override the default config, and to have the option to "not to think about it" commit 42855aea63d390570b5164cc46b7c7a6603610a9 Author: mralj Date: Wed Oct 9 10:10:44 2024 +0200 concurrent map r/w bugfix commit 128b1200798968dcfdacf6b2c981348555c6b6b3 Author: mralj Date: Mon Oct 7 14:00:57 2024 +0200 removed unused import - popped up after rebasing commit ee58cfe525ab1b80fbf6955fcad44a8db81879a0 Author: mralj Date: Mon Oct 7 13:00:42 2024 +0200 missed cleanup of ActivePrecompiles commit d409ef823af742e419aec2f50f84be4f1d6aff80 Author: mralj Date: Mon Oct 7 12:02:42 2024 +0200 bugfixes - l1rpc activated at proper point and precompile address commit bdd7b7d5c90d4549b6f4a0ab57d8fb193045e3ce Author: mralj Date: Mon Oct 7 10:57:48 2024 +0200 ethclient moved to node.Node commit bd56bdc4337b170c5f4969ad570286ebfb365bf2 Author: mralj Date: Fri Oct 4 17:36:36 2024 +0200 code cleanup after trying to merge into arb/op-geth commit 76a23394bc6db848d6e24c829c24b5675c909f33 Author: mralj Date: Tue Oct 1 10:43:04 2024 +0200 internal/ethapi and tracers use pre-existing function call commit b72098e19aca06cf2403da3f89b19cb34a44fb85 Author: mralj Date: Mon Sep 30 10:20:37 2024 +0200 added missing "," - fixed comptime bug commit 1ccbc9589134ebe72eb798f73eabf78a07250b5d Author: mralj Date: Mon Sep 30 10:13:45 2024 +0200 simplified the code commit 0f7439099439dac0fcc8b8d00b7121636206a763 Author: mralj Date: Sun Sep 29 13:12:00 2024 +0200 cleaned up code & created more rollup specific files commit 2a7b7d7b937c167bb167f90c5adca02ebce0812f Author: mralj Date: Sun Sep 29 11:45:34 2024 +0200 cmd - rollup specific files commit ef91bcd578eea9e5b2a7678cb45eb892b13ec2e9 Author: mralj Date: Fri Sep 27 13:10:01 2024 +0200 implements L1SLOAD contract commit 6a98534271adc973fae517ae452177efb0692f60 Author: mralj Date: Thu Sep 26 13:22:58 2024 +0200 added L1SLoad sekelton commit 5f039c50f08baa82b7d9823b06fd4e4ed15aa48e Merge: 204ef24a5 56c1f67d4 Author: mralj Date: Mon Oct 7 13:09:06 2024 +0200 Merge pull request #4 from NethermindEth/core/rip/7728-precompile-impl [P2] Implements RIP-7728 commit 56c1f67d41e2cae55bba3e1a32a1f4bf5cabc88c Author: mralj Date: Sat Sep 28 13:00:06 2024 +0200 added missing mocks for tests commit e9a5c283a73f7775ae7b63eba3bcf355bd09eb9b Author: mralj Date: Fri Sep 27 22:44:44 2024 +0200 added test for too long input edgecase commit bea23a3c5fca4f2bda610ec235e252373f3d762c Author: mralj Date: Fri Sep 27 22:40:37 2024 +0200 added batch call for StoragesAt as well as tests commit c4b24af69ac7940ca9c0fe09a12b55e52564367b Author: mralj Date: Fri Sep 27 13:32:52 2024 +0200 added rpc call timeout strategy commit f0dd2170c62121c7bdc1ba47389b6f1b790d9807 Author: mralj Date: Fri Sep 27 13:10:01 2024 +0200 implements L1SLOAD contract commit 759dda71cec8a5d1353c67fa46bb81dce7ac94b8 Author: mralj Date: Thu Sep 26 13:22:58 2024 +0200 added L1SLoad sekelton commit 204ef24a5f5c992013862f62f05fab36dbe35e3f Author: mralj Date: Thu Sep 26 20:21:47 2024 +0200 added example how code in overrides would change commit a24608eef1bd36176b6a6596c0d7c973400ead1f Author: mralj Date: Thu Sep 26 19:48:47 2024 +0200 added ability to activate rollup precompiles from eth/internal and eth/tracers commit 99ccaf73a55eb655f91c764e00c8a411d7317b55 Author: mralj Date: Thu Sep 26 13:26:08 2024 +0200 bugfix commit 1974d9268529e545582096bccec47fab4083518a Author: mralj Date: Thu Sep 26 13:22:58 2024 +0200 added L1SLoad sekelton commit 0ae7e7b6a1545e6c0001c889e4751c217be7a02a Author: mralj Date: Wed Sep 25 19:08:40 2024 +0200 dial L1 RPC client passed via required flag # Conflicts: # core/vm/interpreter.go # eth/backend.go # params/config.go --- cmd/geth/config.go | 4 + cmd/geth/main.go | 1 + cmd/utils/flags_rollup.go | 32 ++++ core/state_transition.go | 2 +- core/vm/contracts.go | 5 +- core/vm/contracts_rollup.go | 146 ++++++++++++++++++ core/vm/contracts_rollup_overrides.go | 48 ++++++ core/vm/contracts_rollup_test.go | 32 ++++ core/vm/contracts_test.go | 4 +- core/vm/evm.go | 14 +- core/vm/interpreter.go | 3 +- core/vm/runtime/runtime.go | 9 +- .../vm/testdata/precompiles/fail-l1sload.json | 24 +++ core/vm/testdata/precompiles/l1sload.json | 24 +++ eth/tracers/api.go | 4 + eth/tracers/js/goja.go | 10 +- eth/tracers/native/4byte.go | 2 +- eth/tracers/native/call_flat.go | 2 +- ethclient/ethclient_rollup.go | 38 +++++ internal/ethapi/api.go | 13 +- internal/ethapi/api_rollup_test.go | 7 + internal/ethapi/simulate.go | 9 +- internal/flags/categories.go | 1 + node/node.go | 9 ++ node/node_rollup.go | 17 ++ params/config.go | 3 + params/protocol_params_rollup.go | 8 + tests/state_test.go | 4 +- 28 files changed, 447 insertions(+), 28 deletions(-) create mode 100644 cmd/utils/flags_rollup.go create mode 100644 core/vm/contracts_rollup.go create mode 100644 core/vm/contracts_rollup_overrides.go create mode 100644 core/vm/contracts_rollup_test.go create mode 100644 core/vm/testdata/precompiles/fail-l1sload.json create mode 100644 core/vm/testdata/precompiles/l1sload.json create mode 100644 ethclient/ethclient_rollup.go create mode 100644 internal/ethapi/api_rollup_test.go create mode 100644 node/node_rollup.go create mode 100644 params/protocol_params_rollup.go diff --git a/cmd/geth/config.go b/cmd/geth/config.go index df12a831b62f..d6428cf06fef 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -177,6 +177,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { } applyMetricConfig(ctx, &cfg) + //[rollup-geth] + utils.ActivateL1RPCEndpoint(ctx, stack) + return stack, cfg } @@ -281,6 +284,7 @@ func makeFullNode(ctx *cli.Context) *node.Node { utils.Fatalf("failed to register catalyst service: %v", err) } } + return stack } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 2020df3991bb..bdf4bfddefa7 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -275,6 +275,7 @@ func init() { consoleFlags, debug.Flags, metricsFlags, + utils.RollupFlags, //[rollup-geth] ) flags.AutoEnvVars(app.Flags, "GETH") diff --git a/cmd/utils/flags_rollup.go b/cmd/utils/flags_rollup.go new file mode 100644 index 000000000000..bfb6ee3cb538 --- /dev/null +++ b/cmd/utils/flags_rollup.go @@ -0,0 +1,32 @@ +package utils + +import ( + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/internal/flags" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/node" + "github.com/urfave/cli/v2" +) + +var L1NodeRPCEndpointFlag = &cli.StringFlag{ + Name: "rollup.l1.rpc_endpoint", + Usage: "L1 node RPC endpoint eg. http://0.0.0.0:8545", + Category: flags.RollupCategory, +} + +var RollupFlags = []cli.Flag{ + L1NodeRPCEndpointFlag, +} + +// TODO: when we have clearer picture of how do we want rollup "features" (EIPs/RIPs) to be activated +// make this "rule" activated (ie. if not "rule activated" then L1 client can simply be nil) +func ActivateL1RPCEndpoint(ctx *cli.Context, stack *node.Node) { + if !ctx.IsSet(L1NodeRPCEndpointFlag.Name) { + log.Error("L1 node RPC endpoint URL not set", "flag", L1NodeRPCEndpointFlag.Name) + return + } + + l1RPCEndpoint := ctx.String(L1NodeRPCEndpointFlag.Name) + stack.RegisterEthClient(l1RPCEndpoint) + vm.SetVmL1RpcClient(stack.EthClient()) +} diff --git a/core/state_transition.go b/core/state_transition.go index f8b7d2756517..7a90f2ca3d10 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -515,7 +515,7 @@ func (st *StateTransition) innerTransitionDb() (*ExecutionResult, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompilesIncludingRollups(rules), msg.AccessList) var ( ret []byte diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 58146bdc526f..35ae93960244 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -330,6 +330,7 @@ type sha256hash struct{} func (c *sha256hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas } + func (c *sha256hash) Run(input []byte) ([]byte, error) { h := sha256.Sum256(input) return h[:], nil @@ -345,6 +346,7 @@ type ripemd160hash struct{} func (c *ripemd160hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Ripemd160PerWordGas + params.Ripemd160BaseGas } + func (c *ripemd160hash) Run(input []byte) ([]byte, error) { ripemd := ripemd160.New() ripemd.Write(input) @@ -361,6 +363,7 @@ type dataCopy struct{} func (c *dataCopy) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.IdentityPerWordGas + params.IdentityBaseGas } + func (c *dataCopy) Run(in []byte) ([]byte, error) { return common.CopyBytes(in), nil } @@ -510,7 +513,7 @@ func (c *bigModExp) Run(input []byte) ([]byte, error) { // Modulo 0 is undefined, return zero return common.LeftPadBytes([]byte{}, int(modLen)), nil case base.BitLen() == 1: // a bit length of 1 means it's 1 (or -1). - //If base == 1, then we can just return base % mod (if mod >= 1, which it is) + // If base == 1, then we can just return base % mod (if mod >= 1, which it is) v = base.Mod(base, mod).Bytes() default: v = base.Exp(base, exp, mod).Bytes() diff --git a/core/vm/contracts_rollup.go b/core/vm/contracts_rollup.go new file mode 100644 index 000000000000..3d2dfe7d5551 --- /dev/null +++ b/core/vm/contracts_rollup.go @@ -0,0 +1,146 @@ +//[rollup-geth] +// These are rollup-geth specific precompiled contracts + +package vm + +import ( + "context" + "errors" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" +) + +type RollupPrecompileActivationConfig struct { + L1SLoad +} + +type L1RpcClient interface { + StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) +} + +var ( + rollupL1SloadAddress = common.BytesToAddress([]byte{0x01, 0x01}) + precompiledContractsRollupR0 = PrecompiledContracts{ + rollupL1SloadAddress: &L1SLoad{}, + } +) + +func activeRollupPrecompiledContracts(rules params.Rules) PrecompiledContracts { + switch rules.IsR0 { + case rules.IsR0: + return precompiledContractsRollupR0 + default: + return nil + } +} + +// ActivateRollupPrecompiledContracts activates rollup-specific precompiles +func (pc PrecompiledContracts) ActivateRollupPrecompiledContracts(rules params.Rules, config RollupPrecompileActivationConfig) { + activeRollupPrecompiles := activeRollupPrecompiledContracts(rules) + for k, v := range activeRollupPrecompiles { + pc[k] = v + } + + // NOTE: if L1SLoad was not activated via chain rules this is no-op + pc.activateL1SLoad(config.L1RpcClient, config.GetLatestL1BlockNumber) +} + +func ActivePrecompilesIncludingRollups(rules params.Rules) []common.Address { + activePrecompiles := ActivePrecompiles(rules) + activeRollupPrecompiles := activeRollupPrecompiledContracts(rules) + + for k := range activeRollupPrecompiles { + activePrecompiles = append(activePrecompiles, k) + } + + return activePrecompiles +} + +//INPUT SPECS: +//Byte range Name Description +//------------------------------------------------------------ +//[0: 19] (20 bytes) address The contract address +//[20: 51] (32 bytes) key1 The storage key +//... ... ... +//[k*32-12: k*32+19] (32 bytes)key_k The storage key + +type L1SLoad struct { + L1RpcClient L1RpcClient + GetLatestL1BlockNumber func() *big.Int +} + +func (c *L1SLoad) RequiredGas(input []byte) uint64 { + storageSlotsToLoad := len(input[common.AddressLength-1:]) / common.HashLength + storageSlotsToLoad = min(storageSlotsToLoad, params.L1SLoadMaxNumStorageSlots) + + return params.L1SLoadBaseGas + uint64(storageSlotsToLoad)*params.L1SLoadPerLoadGas +} + +func (c *L1SLoad) Run(input []byte) ([]byte, error) { + if !c.isL1SLoadActive() { + log.Error("L1SLOAD called, but not activated", "client", c.L1RpcClient, "and latest block number function", c.GetLatestL1BlockNumber) + return nil, errors.New("L1SLOAD precompile not active") + } + + if len(input) < common.AddressLength+common.HashLength { + return nil, errors.New("L1SLOAD input too short") + } + + countOfStorageKeysToRead := (len(input) - common.AddressLength) / common.HashLength + thereIsAtLeast1StorageKeyToRead := countOfStorageKeysToRead > 0 + allStorageKeysAreExactly32Bytes := countOfStorageKeysToRead*common.HashLength == len(input)-common.AddressLength + + if inputIsInvalid := !(thereIsAtLeast1StorageKeyToRead && allStorageKeysAreExactly32Bytes); inputIsInvalid { + return nil, errors.New("L1SLOAD input is malformed") + } + + contractAddress := common.BytesToAddress(input[:common.AddressLength]) + input = input[common.AddressLength-1:] + contractStorageKeys := make([]common.Hash, countOfStorageKeysToRead) + for k := 0; k < countOfStorageKeysToRead; k++ { + contractStorageKeys[k] = common.BytesToHash(input[k*common.HashLength : (k+1)*common.HashLength]) + } + + var ctx context.Context + if params.L1SLoadRPCTimeoutInSec > 0 { + c, cancel := context.WithTimeout(context.Background(), time.Second*time.Duration(params.L1SLoadRPCTimeoutInSec)) + ctx = c + defer cancel() + } else { + ctx = context.Background() + } + + res, err := c.L1RpcClient.StoragesAt(ctx, contractAddress, contractStorageKeys, c.GetLatestL1BlockNumber()) + if err != nil { + return nil, err + } + + return res, nil +} + +func (c *L1SLoad) isL1SLoadActive() bool { + return c.GetLatestL1BlockNumber != nil && c.L1RpcClient != nil +} + +func (pc PrecompiledContracts) activateL1SLoad(l1RpcClient L1RpcClient, getLatestL1BlockNumber func() *big.Int) { + if precompileNotRuleActivated := pc[rollupL1SloadAddress] == nil; precompileNotRuleActivated { + return + } + + if rpcClientNotOverridenUseDefaultOne := l1RpcClient == nil; rpcClientNotOverridenUseDefaultOne { + l1RpcClient = defaultRollupPrecompilesConfig.L1RpcClient + } + + if latestBlockGetterNotOverridenUseDefaultOne := getLatestL1BlockNumber == nil; latestBlockGetterNotOverridenUseDefaultOne { + getLatestL1BlockNumber = defaultRollupPrecompilesConfig.GetLatestL1BlockNumber + } + + pc[rollupL1SloadAddress] = &L1SLoad{ + L1RpcClient: l1RpcClient, + GetLatestL1BlockNumber: getLatestL1BlockNumber, + } +} diff --git a/core/vm/contracts_rollup_overrides.go b/core/vm/contracts_rollup_overrides.go new file mode 100644 index 000000000000..2ae57ada18d3 --- /dev/null +++ b/core/vm/contracts_rollup_overrides.go @@ -0,0 +1,48 @@ +//[rollup-geth] +// These are rollup-geth specific precompiled contracts + +package vm + +import ( + "math/big" +) + +var defaultRollupPrecompilesConfig RollupPrecompileActivationConfig = RollupPrecompileActivationConfig{ + L1SLoad: L1SLoad{ + GetLatestL1BlockNumber: LetRPCDecideLatestL1Number, + }, +} + +func SetVmL1RpcClient(c L1RpcClient) { + defaultRollupPrecompilesConfig.L1RpcClient = c +} + +// generateRollupPrecompiledContractsOverrides generates rollup precompile config including L2 specific overrides +func generateRollupPrecompiledContractsOverrides(evm *EVM) RollupPrecompileActivationConfig { + return RollupPrecompileActivationConfig{ + L1SLoad{ + L1RpcClient: evm.Config.L1RpcClient, + GetLatestL1BlockNumber: LetRPCDecideLatestL1Number, + }, + } +} + +// [OVERRIDE] LetRPCDecideLatestL1Number +// Each rollup should override this function so that it returns +// correct latest L1 block number +func LetRPCDecideLatestL1Number() *big.Int { + return nil +} + +// [OVERRIDE] getLatestL1BlockNumber +// Each rollup should override this function so that it returns +// correct latest L1 block number +// +// EXAMPLE 2 +// func GetLatestL1BlockNumber(state *state.StateDB) func() *big.Int { +// return func() *big.Int { +// addressOfL1BlockContract := common.Address{} +// slotInContractRepresentingL1BlockNumber := common.Hash{} +// return state.GetState(addressOfL1BlockContract, slotInContractRepresentingL1BlockNumber).Big() +// } +// } diff --git a/core/vm/contracts_rollup_test.go b/core/vm/contracts_rollup_test.go new file mode 100644 index 000000000000..9bde468e659b --- /dev/null +++ b/core/vm/contracts_rollup_test.go @@ -0,0 +1,32 @@ +package vm + +import ( + "context" + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" +) + +type MockL1RPCClient struct{} + +func (m MockL1RPCClient) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + // testcase is in format "abab", this makes output lenght 2 bytes + const mockedRespValueSize = 2 + mockResp := make([]byte, mockedRespValueSize*len(keys)) + for i := range keys { + copy(mockResp[mockedRespValueSize*i:], common.Hex2Bytes("abab")) + } + + return mockResp, nil +} + +func TestPrecompiledL1SLOAD(t *testing.T) { + mockL1RPCClient := MockL1RPCClient{} + + allPrecompiles[rollupL1SloadAddress] = &L1SLoad{} + allPrecompiles.activateL1SLoad(mockL1RPCClient, func() *big.Int { return big1 }) + + testJson("l1sload", rollupL1SloadAddress.Hex(), t) + testJsonFail("l1sload", rollupL1SloadAddress.Hex(), t) +} \ No newline at end of file diff --git a/core/vm/contracts_test.go b/core/vm/contracts_test.go index 414a867f2088..fd6da9850f0c 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -46,7 +46,7 @@ type precompiledFailureTest struct { // allPrecompiles does not map to the actual set of precompiles, as it also contains // repriced versions of precompiles at certain slots -var allPrecompiles = map[common.Address]PrecompiledContract{ +var allPrecompiles = PrecompiledContracts{ common.BytesToAddress([]byte{1}): &ecrecover{}, common.BytesToAddress([]byte{2}): &sha256hash{}, common.BytesToAddress([]byte{3}): &ripemd160hash{}, @@ -184,7 +184,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) { // Keep it as uint64, multiply 100 to get two digit float later mgasps := (100 * 1000 * gasUsed) / elapsed bench.ReportMetric(float64(mgasps)/100, "mgas/s") - //Check if it is correct + // Check if it is correct if err != nil { bench.Error(err) return diff --git a/core/vm/evm.go b/core/vm/evm.go index 1945a3b4dbb6..ffcb54c59cdc 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -119,7 +119,7 @@ type EVM struct { // applied in opCall*. callGasTemp uint64 // precompiles holds the precompiled contracts for the current epoch - precompiles map[common.Address]PrecompiledContract + precompiles PrecompiledContracts } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -133,7 +133,11 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time), } - evm.precompiles = activePrecompiledContracts(evm.chainRules) + + //[rollup-geth] + evm.precompiles = ActivePrecompiledContracts(evm.chainRules) + evm.precompiles.ActivateRollupPrecompiledContracts(evm.chainRules, generateRollupPrecompiledContractsOverrides(evm)) + evm.interpreter = NewEVMInterpreter(evm) return evm } @@ -288,7 +292,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } - var snapshot = evm.StateDB.Snapshot() + snapshot := evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { @@ -340,7 +344,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth } - var snapshot = evm.StateDB.Snapshot() + snapshot := evm.StateDB.Snapshot() // It is allowed to call precompiles, even via delegatecall if p, isPrecompile := evm.precompile(addr); isPrecompile { @@ -390,7 +394,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // after all empty accounts were deleted, so this is not required. However, if we omit this, // then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json. // We could change this, but for now it's left for legacy reasons - var snapshot = evm.StateDB.Snapshot() + snapshot := evm.StateDB.Snapshot() // We do an AddBalance of zero here, just in order to trigger a touch. // This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium, diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index fc7fc21b4ba8..c574f4622465 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -39,11 +39,12 @@ type Config struct { EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled - StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) + StatelessSelfValidation bool // Generate execution witnesses and self-check against them (testing purpose) PrecompileOverrides PrecompileOverrides // Precompiles can be swapped / changed / wrapped as needed NoMaxCodeSize bool // Ignore Max code size and max init code size limits CallerOverride func(v ContractRef) ContractRef // Swap the caller as needed, for VM prank functionality. + L1RpcClient L1RpcClient //[rollup-geth] } // ScopeContext contains the things that are per-call, such as stack and memory, diff --git a/core/vm/runtime/runtime.go b/core/vm/runtime/runtime.go index f83ed682cd13..f552f637d4fb 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -81,7 +81,8 @@ func setDefaults(cfg *Config) { TerminalTotalDifficultyPassed: true, MergeNetsplitBlock: nil, ShanghaiTime: &shanghaiTime, - CancunTime: &cancunTime} + CancunTime: &cancunTime, + } } if cfg.Difficulty == nil { cfg.Difficulty = new(big.Int) @@ -141,7 +142,7 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompilesIncludingRollups(rules), nil) cfg.State.CreateAccount(address) // set the receiver's (the executing contract) code for execution. cfg.State.SetCode(address, code) @@ -177,7 +178,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) { // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) + cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompilesIncludingRollups(rules), nil) // Call the code with the given configuration. code, address, leftOverGas, err := vmenv.Create( sender, @@ -208,7 +209,7 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er // Execute the preparatory steps for state transition which includes: // - prepare accessList(post-berlin) // - reset transient storage(eip 1153) - statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) + statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompilesIncludingRollups(rules), nil) // Call the code with the given configuration. ret, leftOverGas, err := vmenv.Call( diff --git a/core/vm/testdata/precompiles/fail-l1sload.json b/core/vm/testdata/precompiles/fail-l1sload.json new file mode 100644 index 000000000000..43250bede45a --- /dev/null +++ b/core/vm/testdata/precompiles/fail-l1sload.json @@ -0,0 +1,24 @@ +[ + { + "Name": "L1SLOAD FAIL: input contains only address", + "Input": "a83114A443dA1CecEFC50368531cACE9F37fCCcb", + "ExpectedError": "L1SLOAD input too short", + "Gas": 4000, + "NoBenchmark": true + }, + { + "Name": "L1SLOAD FAIL: input key not 32 bytes", + "Input": "a83114A443dA1CecEFC50368531cACE9F37fCCcb112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7122", + "ExpectedError": "L1SLOAD input is malformed", + "Gas": 4000, + "NoBenchmark": true + }, + { + "Name": "L1SLOAD FAIL: input too long", + "Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7aa83114A443dA1CecEFC50368531cACE9F37fCCcb112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7122", + "ExpectedError": "L1SLOAD input is malformed", + "Gas": 4000, + "NoBenchmark": true + } + +] diff --git a/core/vm/testdata/precompiles/l1sload.json b/core/vm/testdata/precompiles/l1sload.json new file mode 100644 index 000000000000..b0debfd3b49a --- /dev/null +++ b/core/vm/testdata/precompiles/l1sload.json @@ -0,0 +1,24 @@ +[ + { + "Name": "L1SLOAD: 1 key", + "Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e864", + "Expected": "abab", + "Gas": 4000, + "NoBenchmark": true + }, + { + "Name": "L1SLOAD: 2 keys", + "Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a", + "Expected": "abababab", + "Gas": 6000, + "NoBenchmark": true + }, + + { + "Name": "L1SLOAD: 5 keys", + "Input": "C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc22d2c7bb6fc06067df8b0223aec460d1ebb51febb9012bc2554141a4dca08e8640112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a112d016b65e9c617ad9ab60604f772a3620177bada4cdc773d9b6a982d3c2a7a", + "Expected": "abababababababababab", + "Gas": 12000, + "NoBenchmark": true + } +] diff --git a/eth/tracers/api.go b/eth/tracers/api.go index d77b4886980e..7964a60d9e05 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1003,6 +1003,10 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc rules := api.backend.ChainConfig().Rules(vmctx.BlockNumber, vmctx.Random != nil, vmctx.Time) precompiles := vm.ActivePrecompiledContracts(rules) + + //[rollup-geth] + precompiles.ActivateRollupPrecompiledContracts(rules, vm.RollupPrecompileActivationConfig{}) + if err := config.StateOverrides.Apply(statedb, precompiles); err != nil { return nil, err } diff --git a/eth/tracers/js/goja.go b/eth/tracers/js/goja.go index b823ef740a86..efc838610ed5 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -62,9 +62,11 @@ func init() { // hex strings into big ints. var bigIntProgram = goja.MustCompile("bigInt", bigIntegerJS, false) -type toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error) -type toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error) -type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) +type ( + toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error) + toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error) + fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) +) func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) { // bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS. @@ -245,7 +247,7 @@ func (t *jsTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from t.dbValue = db.setupObject() // Update list of precompiles based on current block rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) t.ctx["block"] = t.vm.ToValue(t.env.BlockNumber.Uint64()) t.ctx["gas"] = t.vm.ToValue(tx.Gas()) gasPriceBig, err := t.toBig(t.vm, env.GasPrice.String()) diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index 6cb0e433d27d..a5c1fefdef12 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -89,7 +89,7 @@ func (t *fourByteTracer) store(id []byte, size int) { func (t *fourByteTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction, from common.Address) { // Update list of precompiles based on current block rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) } // OnEnter is called when EVM enters a new scope (via call, create or selfdestruct). diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index c869d5593515..8991d9422e2e 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -207,7 +207,7 @@ func (t *flatCallTracer) OnTxStart(env *tracing.VMContext, tx *types.Transaction t.tracer.OnTxStart(env, tx, from) // Update list of precompiles based on current block rules := env.ChainConfig.Rules(env.BlockNumber, env.Random != nil, env.Time) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) } func (t *flatCallTracer) OnTxEnd(receipt *types.Receipt, err error) { diff --git a/ethclient/ethclient_rollup.go b/ethclient/ethclient_rollup.go new file mode 100644 index 000000000000..4571cdca8509 --- /dev/null +++ b/ethclient/ethclient_rollup.go @@ -0,0 +1,38 @@ +package ethclient + +import ( + "context" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" +) + +// StoragesAt returns the values of keys in the contract storage of the given account. +// The block number can be nil, in which case the value is taken from the latest known block. +func (ec *Client) StoragesAt(ctx context.Context, account common.Address, keys []common.Hash, blockNumber *big.Int) ([]byte, error) { + results := make([]hexutil.Bytes, len(keys)) + reqs := make([]rpc.BatchElem, len(keys)) + + for i := range reqs { + reqs[i] = rpc.BatchElem{ + Method: "eth_getStorageAt", + Args: []interface{}{account, keys[i], toBlockNumArg(blockNumber)}, + Result: &results[i], + } + } + if err := ec.c.BatchCallContext(ctx, reqs); err != nil { + return nil, err + } + + output := make([]byte, common.HashLength*len(keys)) + for i := range reqs { + if reqs[i].Error != nil { + return nil, reqs[i].Error + } + copy(output[i*common.HashLength:], results[i]) + } + + return output, nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 7712d71273f3..2b5bbd31af8e 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -257,7 +257,7 @@ func (api *TxPoolAPI) Inspect() map[string]map[string]map[string]string { pending, queue := api.b.TxPoolContent() // Define a formatter to flatten a transaction into a string - var format = func(tx *types.Transaction) string { + format := func(tx *types.Transaction) string { if to := tx.To(); to != nil { return fmt.Sprintf("%s: %v wei + %v gas × %v wei", tx.To().Hex(), tx.Value(), tx.Gas(), tx.GasPrice()) } @@ -1251,6 +1251,11 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S } rules := b.ChainConfig().Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time) precompiles := maps.Clone(vm.ActivePrecompiledContracts(rules)) + + //[rollup-geth] + rollupConfigOverrides := vm.RollupPrecompileActivationConfig{} + precompiles.ActivateRollupPrecompiledContracts(rules, rollupConfigOverrides) + if err := overrides.Apply(state, precompiles); err != nil { return nil, err } @@ -1820,7 +1825,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } isPostMerge := header.Difficulty.Sign() == 0 // Retrieve the precompiles since they don't need to be added to the access list - precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) + precompiles := vm.ActivePrecompilesIncludingRollups(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) // Create an initial tracer prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) @@ -2322,11 +2327,11 @@ func (api *TransactionAPI) Resend(ctx context.Context, sendArgs TransactionArgs, matchTx := sendArgs.ToTransaction(types.LegacyTxType) // Before replacing the old transaction, ensure the _new_ transaction fee is reasonable. - var price = matchTx.GasPrice() + price := matchTx.GasPrice() if gasPrice != nil { price = gasPrice.ToInt() } - var gas = matchTx.Gas() + gas := matchTx.Gas() if gasLimit != nil { gas = uint64(*gasLimit) } diff --git a/internal/ethapi/api_rollup_test.go b/internal/ethapi/api_rollup_test.go new file mode 100644 index 000000000000..337dca43e256 --- /dev/null +++ b/internal/ethapi/api_rollup_test.go @@ -0,0 +1,7 @@ +package ethapi + +import "github.com/ethereum/go-ethereum/core/vm" + +func (b *testBackend) GetL1RpcClient() vm.L1RpcClient { + return nil +} diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index c42ede29db1c..ea06de17e7f1 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -286,10 +286,15 @@ func (sim *simulator) sanitizeCall(call *TransactionArgs, state *state.StateDB, func (sim *simulator) activePrecompiles(base *types.Header) vm.PrecompiledContracts { var ( - isMerge = (base.Difficulty.Sign() == 0) + isMerge = base.Difficulty.Sign() == 0 rules = sim.chainConfig.Rules(base.Number, isMerge, base.Time) ) - return maps.Clone(vm.ActivePrecompiledContracts(rules)) + + precompiles := vm.ActivePrecompiledContracts(rules) + //[rollup-geth] + precompiles.ActivateRollupPrecompiledContracts(rules, vm.RollupPrecompileActivationConfig{}) + + return maps.Clone(precompiles) } // sanitizeChain checks the chain integrity. Specifically it checks that diff --git a/internal/flags/categories.go b/internal/flags/categories.go index d9c2d9894afa..e3e2e1547fff 100644 --- a/internal/flags/categories.go +++ b/internal/flags/categories.go @@ -38,6 +38,7 @@ const ( MiscCategory = "MISC" TestingCategory = "TESTING" DeprecatedCategory = "ALIASED (deprecated)" + RollupCategory = "ROLLUP" //[rollup-geth] ) func init() { diff --git a/node/node.go b/node/node.go index d8c5994be0a6..c334de875d5f 100644 --- a/node/node.go +++ b/node/node.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/ethdb/memorydb" "github.com/ethereum/go-ethereum/event" @@ -67,6 +68,8 @@ type Node struct { inprocHandler *rpc.Server // In-process RPC request handler to process the API requests databases map[*closeTrackingDB]struct{} // All open databases + + ethClient *ethclient.Client } const ( @@ -708,6 +711,12 @@ func (n *Node) EventMux() *event.TypeMux { return n.eventmux } +// [rollup-geth] +// EthClient returns instance of ETH RPC client +func (n *Node) EthClient() *ethclient.Client { + return n.ethClient +} + // OpenDatabase opens an existing database with the given name (or creates one if no // previous can be found) from within the node's instance directory. If the node is // ephemeral, a memory database is returned. diff --git a/node/node_rollup.go b/node/node_rollup.go new file mode 100644 index 000000000000..5d0f0e8b042d --- /dev/null +++ b/node/node_rollup.go @@ -0,0 +1,17 @@ +package node + +import ( + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/log" +) + +func (n *Node) RegisterEthClient(endpoint string) { + ethClient, err := ethclient.Dial(endpoint) + if err != nil { + log.Error("Unable to connect to ETH RPC endpoint at", "URL", ethClient, "error", err) + return + } + + n.ethClient = ethClient + log.Info("Initialized ETH RPC client", "endpoint", ethClient) +} diff --git a/params/config.go b/params/config.go index 04cc43a84037..00b35491a03a 100644 --- a/params/config.go +++ b/params/config.go @@ -1078,6 +1078,7 @@ type Rules struct { IsOptimismBedrock, IsOptimismRegolith bool IsOptimismCanyon, IsOptimismFjord bool IsOptimismGranite, IsOptimismHolocene bool + IsR0 bool // [rollup-geth] } // Rules ensures c's ChainID is not nil. @@ -1115,5 +1116,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsOptimismFjord: isMerge && c.IsOptimismFjord(timestamp), IsOptimismGranite: isMerge && c.IsOptimismGranite(timestamp), IsOptimismHolocene: isMerge && c.IsOptimismHolocene(timestamp), + + IsR0: true, // [rollup-geth] } } diff --git a/params/protocol_params_rollup.go b/params/protocol_params_rollup.go new file mode 100644 index 000000000000..d75663d81c78 --- /dev/null +++ b/params/protocol_params_rollup.go @@ -0,0 +1,8 @@ +package params + +const ( + L1SLoadBaseGas uint64 = 2000 // Base price for L1Sload + L1SLoadPerLoadGas uint64 = 2000 // Per-load price for loading one storage slot + L1SLoadMaxNumStorageSlots = 5 // Max number of storage slots requested in L1Sload precompile + L1SLoadRPCTimeoutInSec = 3 +) diff --git a/tests/state_test.go b/tests/state_test.go index 95233c3b1032..0230babe3bd8 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -262,7 +262,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.Error(err) return } - var rules = config.Rules(new(big.Int), false, 0) + rules := config.Rules(new(big.Int), false, 0) vmconfig.ExtraEips = eips block := t.genesis(config).ToBlock() @@ -319,7 +319,7 @@ func runBenchmark(b *testing.B, t *StateTest) { b.ResetTimer() for n := 0; n < b.N; n++ { snapshot := state.StateDB.Snapshot() - state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList) + state.StateDB.Prepare(rules, msg.From, context.Coinbase, msg.To, vm.ActivePrecompilesIncludingRollups(rules), msg.AccessList) b.StartTimer() start := time.Now() From 562417b866b78f00330a7a139518a4f7d7c4b4b2 Mon Sep 17 00:00:00 2001 From: mralj Date: Fri, 11 Oct 2024 13:15:17 +0200 Subject: [PATCH 3/3] bugfix --- internal/flags/categories.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/flags/categories.go b/internal/flags/categories.go index e3e2e1547fff..d9c2d9894afa 100644 --- a/internal/flags/categories.go +++ b/internal/flags/categories.go @@ -38,7 +38,6 @@ const ( MiscCategory = "MISC" TestingCategory = "TESTING" DeprecatedCategory = "ALIASED (deprecated)" - RollupCategory = "ROLLUP" //[rollup-geth] ) func init() {