diff --git a/cmd/geth/config.go b/cmd/geth/config.go index fd57ff40def4..92dc9f8a2152 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 } @@ -250,6 +253,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 2675a616759c..ff5aa67d3157 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -257,6 +257,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 6db88ff66183..8fe5ca0473d4 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -1861,7 +1861,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 d285d03fe245..808ed5d78cfd 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -437,7 +437,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 ( ret []byte diff --git a/core/vm/contracts.go b/core/vm/contracts.go index 104d2ba814b2..d9ee58d25a17 100644 --- a/core/vm/contracts.go +++ b/core/vm/contracts.go @@ -281,6 +281,7 @@ type sha256hash struct{} func (c *sha256hash) RequiredGas(input []byte) uint64 { return uint64(len(input)+31)/32*params.Sha256PerWordGas + params.Sha256BaseGas } + func (c *sha256hash) Run(input []byte) ([]byte, error) { h := sha256.Sum256(input) return h[:], nil @@ -296,6 +297,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) @@ -312,6 +314,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 } @@ -461,7 +464,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 fff5c966f34f..23035e0eeec2 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{}, @@ -181,7 +181,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 616668d565cc..cc4b207e4e5d 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -113,7 +113,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 @@ -127,7 +127,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 } @@ -273,7 +277,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 { @@ -324,7 +328,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 { @@ -373,7 +377,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 793f398367a7..6e0b84067032 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -34,7 +34,8 @@ 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) + 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/backend.go b/eth/backend.go index f10d99c3a70b..409c0a14bbad 100644 --- a/eth/backend.go +++ b/eth/backend.go @@ -164,7 +164,7 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { shutdownTracker: shutdowncheck.NewShutdownTracker(chainDb), } bcVersion := rawdb.ReadDatabaseVersion(chainDb) - var dbVer = "" + dbVer := "" if bcVersion != nil { dbVer = fmt.Sprintf("%d", *bcVersion) } @@ -215,10 +215,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) { if config.OverrideVerkle != nil { overrides.OverrideVerkle = config.OverrideVerkle } + eth.blockchain, err = core.NewBlockChain(chainDb, cacheConfig, config.Genesis, &overrides, eth.engine, vmConfig, &config.TransactionHistory) if err != nil { return nil, err } + eth.bloomIndexer.Start(eth.blockchain) if config.BlobPool.Datadir != "" { diff --git a/eth/tracers/api.go b/eth/tracers/api.go index a8289512069b..ec7a48d39add 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -960,6 +960,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 a47b79f8df26..b3872b111528 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 a50295289340..37044b6f37a9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -256,7 +256,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()) } @@ -1166,6 +1166,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 } @@ -1639,7 +1644,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) @@ -2092,11 +2097,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 4371a4246480..d8e5ba12453b 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -283,10 +283,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 d426add55b10..7f28f24f9097 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 633f88f058a1..02e0d38ec245 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 9ecf465bb67a..f405622951ec 100644 --- a/params/config.go +++ b/params/config.go @@ -894,6 +894,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. @@ -924,5 +925,6 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules IsPrague: isMerge && c.IsPrague(num, timestamp), IsVerkle: isVerkle, IsEIP4762: isVerkle, + 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 76fec97de0ee..e3e8dd6f8bce 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()