From e712712e97f5714bb5c94f94e4efaf0fa227a6a2 Mon Sep 17 00:00:00 2001 From: mralj Date: Fri, 11 Oct 2024 12:47:58 +0200 Subject: [PATCH] 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: # cmd/geth/config.go # core/vm/evm.go # core/vm/interpreter.go # core/vm/runtime/runtime.go # eth/backend.go # eth/tracers/api.go # eth/tracers/js/goja.go # eth/tracers/native/4byte.go # eth/tracers/native/call_flat.go # internal/ethapi/api.go # internal/ethapi/simulate.go # node/node.go # params/config.go # tests/state_test.go --- cmd/geth/config.go | 3 + cmd/geth/main.go | 1 + cmd/utils/flags.go | 2 +- cmd/utils/flags_rollup.go | 32 ++++ core/state_transition.go | 2 +- core/vm/contracts.go | 33 +++- core/vm/contracts_rollup.go | 148 ++++++++++++++++++ core/vm/contracts_rollup_overrides.go | 48 ++++++ core/vm/contracts_rollup_test.go | 32 ++++ core/vm/contracts_test.go | 4 +- core/vm/evm.go | 27 ++-- core/vm/interpreter.go | 1 + core/vm/runtime/runtime.go | 6 +- .../vm/testdata/precompiles/fail-l1sload.json | 24 +++ core/vm/testdata/precompiles/l1sload.json | 24 +++ eth/tracers/js/goja.go | 2 +- eth/tracers/native/4byte.go | 2 +- eth/tracers/native/call_flat.go | 2 +- ethclient/ethclient_rollup.go | 38 +++++ internal/ethapi/api.go | 2 +- internal/ethapi/api_rollup_test.go | 7 + internal/flags/categories.go | 1 + node/node.go | 8 + node/node_rollup.go | 17 ++ params/config.go | 2 + params/protocol_params_rollup.go | 8 + tests/state_test.go | 2 +- 27 files changed, 447 insertions(+), 31 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 5f52f1df5442..dbc69b873683 100644 --- a/cmd/geth/config.go +++ b/cmd/geth/config.go @@ -163,6 +163,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) { } applyMetricConfig(ctx, &cfg) + //[rollup-geth] + utils.ActivateL1RPCEndpoint(ctx, stack) + return stack, cfg } diff --git a/cmd/geth/main.go b/cmd/geth/main.go index 43a93bd48599..c6a939af1bad 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -247,6 +247,7 @@ func init() { consoleFlags, debug.Flags, metricsFlags, + utils.RollupFlags, //[rollup-geth] ) flags.AutoEnvVars(app.Flags, "GETH") diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index d1518a136f2e..513e173cb364 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1809,7 +1809,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) { if rawdb.ReadCanonicalHash(chaindb, 0) != (common.Hash{}) { cfg.Genesis = nil // fallback to db content - //validate genesis has PoS enabled in block 0 + // validate genesis has PoS enabled in block 0 genesis, err := core.ReadGenesis(chaindb) if err != nil { Fatalf("Could not read genesis from database: %v", err) 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 9f1d8f4b1620..2c72436d5386 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -484,7 +484,7 @@ func (st *StateTransition) TransitionDb() (*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 deployedContract *common.Address diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 0b20cd38a336..96f141899822 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -21,6 +21,7 @@ import ( "encoding/binary" "errors" "fmt" + "maps" "math/big" "github.com/ethereum/go-ethereum/common" @@ -43,6 +44,9 @@ type PrecompiledContract interface { Run(input []byte) ([]byte, error) // Run runs the precompiled contract } +// PrecompiledContracts contains the precompiled contracts supported at the given fork. +type PrecompiledContracts map[common.Address]PrecompiledContract + // PrecompiledContractsHomestead contains the default set of pre-compiled Ethereum // contracts used in the Frontier and Homestead releases. var PrecompiledContractsHomestead = map[common.Address]PrecompiledContract{ @@ -154,6 +158,30 @@ func init() { } } +func activePrecompiledContracts(rules params.Rules) PrecompiledContracts { + switch { + case rules.IsStylus: + return PrecompiledContractsArbOS30 + case rules.IsArbitrum: + return PrecompiledContractsArbitrum + case rules.IsCancun: + return PrecompiledContractsCancun + case rules.IsBerlin: + return PrecompiledContractsBerlin + case rules.IsIstanbul: + return PrecompiledContractsIstanbul + case rules.IsByzantium: + return PrecompiledContractsByzantium + default: + return PrecompiledContractsHomestead + } +} + +// ActivePrecompiledContracts returns a copy of precompiled contracts enabled with the current configuration. +func ActivePrecompiledContracts(rules params.Rules) PrecompiledContracts { + return maps.Clone(activePrecompiledContracts(rules)) +} + // ActivePrecompiles returns the precompiles enabled with the current configuration. func ActivePrecompiles(rules params.Rules) []common.Address { switch { @@ -256,6 +284,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 @@ -271,6 +300,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) @@ -287,6 +317,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 } @@ -439,7 +470,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..35ba920cccbd --- /dev/null +++ b/core/vm/contracts_rollup.go @@ -0,0 +1,148 @@ +//[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 + if storageSlotsToLoad > params.L1SLoadMaxNumStorageSlots { + 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 4d7d2b45de94..9ac386fc8f8b 100644 --- a/core/vm/contracts_test.go +++ b/core/vm/contracts_test.go @@ -45,7 +45,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{}, @@ -183,7 +183,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 c530a0ef0eba..309ef2028c62 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -40,24 +40,7 @@ type ( ) func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) { - var precompiles map[common.Address]PrecompiledContract - switch { - case evm.chainRules.IsStylus: - precompiles = PrecompiledContractsArbOS30 - case evm.chainRules.IsArbitrum: - precompiles = PrecompiledContractsArbitrum - case evm.chainRules.IsCancun: - precompiles = PrecompiledContractsCancun - case evm.chainRules.IsBerlin: - precompiles = PrecompiledContractsBerlin - case evm.chainRules.IsIstanbul: - precompiles = PrecompiledContractsIstanbul - case evm.chainRules.IsByzantium: - precompiles = PrecompiledContractsByzantium - default: - precompiles = PrecompiledContractsHomestead - } - p, ok := precompiles[addr] + p, ok := evm.precompiles[addr] return p, ok } @@ -133,6 +116,8 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 + // precompiles holds the precompiled contracts for the current epoch + precompiles PrecompiledContracts } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -158,7 +143,13 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig chainConfig: chainConfig, chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time, blockCtx.ArbOSVersion), } + evm.ProcessingHook = DefaultTxProcessor{evm: evm} + + //[rollup-geth] + evm.precompiles = ActivePrecompiledContracts(evm.chainRules) + evm.precompiles.ActivateRollupPrecompiledContracts(evm.chainRules, generateRollupPrecompiledContractsOverrides(evm)) + evm.interpreter = NewEVMInterpreter(evm) return evm } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 901425213d60..b2cef473b3aa 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -30,6 +30,7 @@ type Config struct { NoBaseFee bool // Forces the EIP-1559 baseFee to 0 (needed for 0 price calls) EnablePreimageRecording bool // Enables recording of SHA3/keccak preimages ExtraEips []int // Additional EIPS that are to be enabled + 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 8e398c3c2e79..d346ad16cabb 100644 --- a/core/vm/runtime/runtime.go +++ b/core/vm/runtime/runtime.go @@ -126,7 +126,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) @@ -159,7 +159,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, @@ -187,7 +187,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/js/goja.go b/eth/tracers/js/goja.go index bf7a48a63400..cc94ea90846f 100644 --- a/eth/tracers/js/goja.go +++ b/eth/tracers/js/goja.go @@ -280,7 +280,7 @@ func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Addr t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64()) // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time, env.Context.ArbOSVersion) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) } // CaptureState implements the Tracer interface to trace a single step of VM execution. diff --git a/eth/tracers/native/4byte.go b/eth/tracers/native/4byte.go index ddc0b56904e4..5ebb4cddfda8 100644 --- a/eth/tracers/native/4byte.go +++ b/eth/tracers/native/4byte.go @@ -82,7 +82,7 @@ func (t *fourByteTracer) store(id []byte, size int) { func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) { // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time, env.Context.ArbOSVersion) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) // Save the outer calldata also if len(input) >= 4 { diff --git a/eth/tracers/native/call_flat.go b/eth/tracers/native/call_flat.go index 3d888decc370..c071968057eb 100644 --- a/eth/tracers/native/call_flat.go +++ b/eth/tracers/native/call_flat.go @@ -166,7 +166,7 @@ func (t *flatCallTracer) CaptureStart(env *vm.EVM, from common.Address, to commo t.tracer.CaptureStart(env, from, to, create, input, gas, value) // Update list of precompiles based on current block rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil, env.Context.Time, env.Context.ArbOSVersion) - t.activePrecompiles = vm.ActivePrecompiles(rules) + t.activePrecompiles = vm.ActivePrecompilesIncludingRollups(rules) } // CaptureEnd is called after the call finishes to finalize the tracing. 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 f745c193d6a1..6556f1f023ef 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1715,7 +1715,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } isPostMerge := header.Difficulty.Cmp(common.Big0) == 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, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion)) + precompiles := vm.ActivePrecompilesIncludingRollups(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time, types.DeserializeHeaderExtraInformation(header).ArbOSFormatVersion)) // Create an initial tracer prevTracer := logger.NewAccessListTracer(nil, args.from(), to, precompiles) 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/flags/categories.go b/internal/flags/categories.go index 3ff0767921b9..96c42dbf71ad 100644 --- a/internal/flags/categories.go +++ b/internal/flags/categories.go @@ -37,6 +37,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 8c72f968a3e2..536283b16236 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/pebble" "github.com/ethereum/go-ethereum/event" @@ -72,6 +73,7 @@ type Node struct { databases map[*closeTrackingDB]struct{} // All open databases apiFilter map[string]bool // Whitelisting API methods + ethClient *ethclient.Client } const ( @@ -755,6 +757,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 69d846e9ae6b..850fb0ba9ac7 100644 --- a/params/config.go +++ b/params/config.go @@ -924,6 +924,7 @@ type Rules struct { IsBerlin, IsLondon bool IsMerge, IsShanghai, IsCancun, IsPrague bool IsVerkle bool + IsR0 bool // [rollup-geth] } // Rules ensures c's ChainID is not nil. @@ -954,5 +955,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64, curren IsCancun: isMerge && c.IsCancun(num, timestamp, currentArbosVersion), IsPrague: isMerge && c.IsPrague(num, timestamp), IsVerkle: isMerge && c.IsVerkle(num, 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 23f0315049a8..80ef1cf9abc5 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -301,7 +301,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()