Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Arbitrum][Integrate]RIP-7728 #10

Open
wants to merge 1 commit into
base: arbitrum
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
}
applyMetricConfig(ctx, &cfg)

//[rollup-geth]
utils.ActivateL1RPCEndpoint(ctx, stack)

return stack, cfg
}

Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ func init() {
consoleFlags,
debug.Flags,
metricsFlags,
utils.RollupFlags, //[rollup-geth]
)
flags.AutoEnvVars(app.Flags, "GETH")

Expand Down
2 changes: 1 addition & 1 deletion cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 32 additions & 0 deletions cmd/utils/flags_rollup.go
Original file line number Diff line number Diff line change
@@ -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())
}
2 changes: 1 addition & 1 deletion core/state_transition.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
33 changes: 32 additions & 1 deletion core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"maps"
"math/big"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -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{
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down
148 changes: 148 additions & 0 deletions core/vm/contracts_rollup.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
48 changes: 48 additions & 0 deletions core/vm/contracts_rollup_overrides.go
Original file line number Diff line number Diff line change
@@ -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()
// }
// }
32 changes: 32 additions & 0 deletions core/vm/contracts_rollup_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading