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

Implements RIP-7728 #2

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0ae7e7b
dial L1 RPC client passed via required flag
mralj Sep 25, 2024
1974d92
added L1SLoad sekelton
mralj Sep 26, 2024
99ccaf7
bugfix
mralj Sep 26, 2024
a24608e
added ability to activate rollup precompiles from eth/internal and et…
mralj Sep 26, 2024
204ef24
added example how code in overrides would change
mralj Sep 26, 2024
759dda7
added L1SLoad sekelton
mralj Sep 26, 2024
f0dd217
implements L1SLOAD contract
mralj Sep 27, 2024
c4b24af
added rpc call timeout strategy
mralj Sep 27, 2024
bea23a3
added batch call for StoragesAt as well as tests
mralj Sep 27, 2024
e9a5c28
added test for too long input edgecase
mralj Sep 27, 2024
56c1f67
added missing mocks for tests
mralj Sep 28, 2024
5f039c5
Merge pull request #4 from NethermindEth/core/rip/7728-precompile-impl
mralj Oct 7, 2024
6a98534
added L1SLoad sekelton
mralj Sep 26, 2024
ef91bcd
implements L1SLOAD contract
mralj Sep 27, 2024
2a7b7d7
cmd - rollup specific files
mralj Sep 29, 2024
0f74390
cleaned up code & created more rollup specific files
mralj Sep 29, 2024
1ccbc95
simplified the code
mralj Sep 30, 2024
b72098e
added missing "," - fixed comptime bug
mralj Sep 30, 2024
76a2339
internal/ethapi and tracers use pre-existing function call
mralj Oct 1, 2024
bd56bdc
code cleanup after trying to merge into arb/op-geth
mralj Oct 4, 2024
bdd7b7d
ethclient moved to node.Node
mralj Oct 7, 2024
d409ef8
bugfixes - l1rpc activated at proper point and precompile address
mralj Oct 7, 2024
ee58cfe
missed cleanup of ActivePrecompiles
mralj Oct 7, 2024
128b120
removed unused import - popped up after rebasing
mralj Oct 7, 2024
42855ae
concurrent map r/w bugfix
mralj Oct 9, 2024
d4cd646
rollup precompile config is glob. variable
mralj Oct 11, 2024
237b78f
removed unnecessary call to vm.SetVmL1RpcClient
mralj Oct 11, 2024
ae96b7b
Merge pull request #5 from NethermindEth/feat/rollup-specific-files
mralj Oct 12, 2024
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
4 changes: 4 additions & 0 deletions cmd/geth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
}
applyMetricConfig(ctx, &cfg)

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

return stack, cfg
}

Expand Down Expand Up @@ -250,6 +253,7 @@ func makeFullNode(ctx *cli.Context) *node.Node {
utils.Fatalf("failed to register catalyst service: %v", err)
}
}

return stack
}

Expand Down
1 change: 1 addition & 0 deletions cmd/geth/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,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 @@ -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)
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 @@ -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
Expand Down
5 changes: 4 additions & 1 deletion core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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
}
Expand Down Expand Up @@ -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()
Expand Down
146 changes: 146 additions & 0 deletions core/vm/contracts_rollup.go
Original file line number Diff line number Diff line change
@@ -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,
}
}
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)
}
4 changes: 2 additions & 2 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{},
Expand Down Expand Up @@ -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
Expand Down
Loading
Loading