Skip to content

Commit

Permalink
Add local mode execution (#185)
Browse files Browse the repository at this point in the history
* Add local mode execution

* Add unit tests

* Add warning about confidential store

* Add more info about conf store

* Address feedback
  • Loading branch information
ferranbt authored Feb 5, 2024
1 parent 0c1e8d8 commit a5e27fb
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 22 deletions.
165 changes: 143 additions & 22 deletions cmd/geth/forgecmd.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,135 @@
package main

import (
"bytes"
"context"
"encoding/hex"
"fmt"
"math/big"
"os"
"reflect"
"strings"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/ethapi"
"github.com/ethereum/go-ethereum/rpc"
"github.com/ethereum/go-ethereum/suave/artifacts"
suave_backends "github.com/ethereum/go-ethereum/suave/backends"
suave "github.com/ethereum/go-ethereum/suave/core"
"github.com/flashbots/go-boost-utils/bls"
"github.com/naoina/toml"
"github.com/urfave/cli/v2"
)

var defaultRemoteSuaveHost = "http://localhost:8545"

var (
isLocalForgeFlag = &cli.BoolFlag{
Name: "local",
Usage: `Whether to run the query command locally`,
}
whiteListForgeFlag = &cli.StringSliceFlag{
Name: "whitelist",
Usage: `The whitelist external endpoints to call`,
}
ethBackendForgeFlag = &cli.StringFlag{
Name: "eth-backend",
Usage: `The endpoint of the confidential eth backend`,
}
tomlConfigForgeFlag = &cli.StringFlag{
Name: "config",
Usage: `The path to the forge toml config file`,
}
)

type suaveForgeConfig struct {
Whitelist []string `toml:"whitelist"`
EthBackend string `toml:"eth_backend"`
}

func readContext(ctx *cli.Context) (*vm.SuaveContext, error) {
// try to read the config from the toml config file
cfg := &suaveForgeConfig{}

if ctx.IsSet(tomlConfigForgeFlag.Name) {
// read the toml config file
data, err := os.ReadFile(ctx.String(tomlConfigForgeFlag.Name))
if err != nil {
return nil, err
}

// this is decoding
// [profile.suave]
var config struct {
Profile struct {
Suave *suaveForgeConfig
}
}

tomlConfig := toml.DefaultConfig
tomlConfig.MissingField = func(rt reflect.Type, field string) error {
return nil
}
if err := tomlConfig.NewDecoder(bytes.NewReader(data)).Decode(&config); err != nil {
return nil, err
}
cfg = config.Profile.Suave
}

// override the config if the flags are set
if ctx.IsSet(ethBackendForgeFlag.Name) {
cfg.EthBackend = ctx.String(ethBackendForgeFlag.Name)
}
if ctx.IsSet(whiteListForgeFlag.Name) {
cfg.Whitelist = ctx.StringSlice(whiteListForgeFlag.Name)
}

// create the suave context
var suaveEthBackend suave.ConfidentialEthBackend
if cfg.EthBackend != "" {
suaveEthBackend = suave_backends.NewRemoteEthBackend(cfg.EthBackend)
} else {
suaveEthBackend = &suave_backends.EthMock{}
}

ecdsaKey, err := crypto.GenerateKey()
if err != nil {
return nil, err
}
blsKey, err := bls.GenerateRandomSecretKey()
if err != nil {
return nil, err
}

// NOTE: the confidential store precompiles are not enabled since they are stateless
backend := &vm.SuaveExecutionBackend{
ExternalWhitelist: cfg.Whitelist,
ConfidentialEthBackend: suaveEthBackend,
EthBlockSigningKey: blsKey,
EthBundleSigningKey: ecdsaKey,
}
suaveCtx := &vm.SuaveContext{
Backend: backend,
}
return suaveCtx, nil
}

var (
forgeCommand = &cli.Command{
Name: "forge",
Usage: "Internal command for MEVM forge commands",
ArgsUsage: "",
Description: `Internal command used by MEVM precompiles in forge to access the MEVM API utilities.`,
Flags: []cli.Flag{
isLocalForgeFlag,
whiteListForgeFlag,
ethBackendForgeFlag,
tomlConfigForgeFlag,
},
Subcommands: []*cli.Command{
forgeStatusCmd,
resetConfStore,
Expand Down Expand Up @@ -56,34 +163,48 @@ var (
return fmt.Errorf("failed to decode input: %w", err)
}

rpcClient, err := rpc.Dial(defaultRemoteSuaveHost)
if err != nil {
return fmt.Errorf("failed to dial rpc: %w", err)
}
if ctx.IsSet(isLocalForgeFlag.Name) {
suaveCtx, err := readContext(ctx)
if err != nil {
return fmt.Errorf("failed to read context: %w", err)
}

ethClient := ethclient.NewClient(rpcClient)
result, err := vm.NewSuavePrecompiledContractWrapper(common.HexToAddress(addr), suaveCtx).Run(input)
if err != nil {
return fmt.Errorf("failed to run precompile: %w", err)
}
fmt.Println(hex.EncodeToString(result))
} else {
rpcClient, err := rpc.Dial(defaultRemoteSuaveHost)
if err != nil {
return fmt.Errorf("failed to dial rpc: %w", err)
}

chainIdRaw, err := ethClient.ChainID(context.Background())
if err != nil {
return fmt.Errorf("failed to get chain id: %w", err)
}
ethClient := ethclient.NewClient(rpcClient)

chainId := hexutil.Big(*chainIdRaw)
toAddr := common.HexToAddress(addr)
chainIdRaw, err := ethClient.ChainID(context.Background())
if err != nil {
return fmt.Errorf("failed to get chain id: %w", err)
}

callArgs := ethapi.TransactionArgs{
To: &toAddr,
IsConfidential: true,
ChainID: &chainId,
Data: (*hexutil.Bytes)(&input),
}
var simResult hexutil.Bytes
if err := rpcClient.Call(&simResult, "eth_call", setTxArgsDefaults(callArgs), "latest"); err != nil {
return err
chainId := hexutil.Big(*chainIdRaw)
toAddr := common.HexToAddress(addr)

callArgs := ethapi.TransactionArgs{
To: &toAddr,
IsConfidential: true,
ChainID: &chainId,
Data: (*hexutil.Bytes)(&input),
}
var simResult hexutil.Bytes
if err := rpcClient.Call(&simResult, "eth_call", setTxArgsDefaults(callArgs), "latest"); err != nil {
return err
}

// return the result without the 0x prefix
fmt.Println(simResult.String()[2:])
}

// return the result without the 0x prefix
fmt.Println(simResult.String()[2:])
return nil
},
}
Expand Down
56 changes: 56 additions & 0 deletions cmd/geth/forgecmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package main

import (
"flag"
"io"
"testing"

suave_backends "github.com/ethereum/go-ethereum/suave/backends"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)

func flagSet(t *testing.T, flags []cli.Flag) *flag.FlagSet {
set := flag.NewFlagSet("test", flag.ContinueOnError)

for _, f := range flags {
if err := f.Apply(set); err != nil {
t.Fatal(err)
}
}
set.SetOutput(io.Discard)
return set
}

func TestForgeReadConfig(t *testing.T) {
t.Parallel()

ctx := cli.NewContext(nil, flagSet(t, forgeCommand.Flags), nil)

// read context from config toml file
ctx.Set("config", "./testdata/forge.toml")

sCtx, err := readContext(ctx)
require.NoError(t, err)
require.Equal(t, sCtx.Backend.ExternalWhitelist, []string{"a", "b"})
require.Equal(t, sCtx.Backend.ConfidentialEthBackend.(*suave_backends.RemoteEthBackend).Endpoint(), "suave")

// override the config if the flags are set
ctx.Set("eth-backend", "http://localhost:8545")
ctx.Set("whitelist", "c,d")

sCtx, err = readContext(ctx)
require.NoError(t, err)
require.Equal(t, sCtx.Backend.ExternalWhitelist, []string{"c", "d"})
require.Equal(t, sCtx.Backend.ConfidentialEthBackend.(*suave_backends.RemoteEthBackend).Endpoint(), "http://localhost:8545")

// set flags to null and use default values
ctx = cli.NewContext(nil, flagSet(t, forgeCommand.Flags), nil)

sCtx, err = readContext(ctx)
require.NoError(t, err)
require.Len(t, sCtx.Backend.ExternalWhitelist, 0)

_, ok := sCtx.Backend.ConfidentialEthBackend.(*suave_backends.EthMock)
require.True(t, ok)
}
6 changes: 6 additions & 0 deletions cmd/geth/testdata/forge.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.suave]
whitelist = ["a", "b"]
eth_backend = "suave"
[profile.ci.fuzz]
runs = 10_000
solc_version = "0.8.23"
16 changes: 16 additions & 0 deletions core/vm/contracts_suave.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ func (b *suaveRuntime) confidentialInputs() ([]byte, error) {
/* Confidential store precompiles */

func (b *suaveRuntime) confidentialStore(dataId types.DataId, key string, data []byte) error {
if b.suaveContext.Backend.ConfidentialStore == nil {
return fmt.Errorf("confidential store is not enabled")
}

record, err := b.suaveContext.Backend.ConfidentialStore.FetchRecordByID(dataId)
if err != nil {
return suave.ErrRecordNotFound
Expand All @@ -65,6 +69,10 @@ func (b *suaveRuntime) confidentialStore(dataId types.DataId, key string, data [
}

func (b *suaveRuntime) confidentialRetrieve(dataId types.DataId, key string) ([]byte, error) {
if b.suaveContext.Backend.ConfidentialStore == nil {
return nil, fmt.Errorf("confidential store is not enabled")
}

record, err := b.suaveContext.Backend.ConfidentialStore.FetchRecordByID(dataId)
if err != nil {
return nil, suave.ErrRecordNotFound
Expand All @@ -90,6 +98,10 @@ func (b *suaveRuntime) confidentialRetrieve(dataId types.DataId, key string) ([]
/* Data Record precompiles */

func (b *suaveRuntime) newDataRecord(decryptionCondition uint64, allowedPeekers []common.Address, allowedStores []common.Address, RecordType string) (types.DataRecord, error) {
if b.suaveContext.Backend.ConfidentialStore == nil {
return types.DataRecord{}, fmt.Errorf("confidential store is not enabled")
}

record, err := b.suaveContext.Backend.ConfidentialStore.InitRecord(types.DataRecord{
Salt: suave.RandomDataRecordId(),
DecryptionCondition: decryptionCondition,
Expand All @@ -105,6 +117,10 @@ func (b *suaveRuntime) newDataRecord(decryptionCondition uint64, allowedPeekers
}

func (b *suaveRuntime) fetchDataRecords(targetBlock uint64, namespace string) ([]types.DataRecord, error) {
if b.suaveContext.Backend.ConfidentialStore == nil {
return nil, fmt.Errorf("confidential store is not enabled")
}

records1 := b.suaveContext.Backend.ConfidentialStore.FetchRecordsByProtocolAndBlock(targetBlock, namespace)

records := make([]types.DataRecord, 0, len(records1))
Expand Down
4 changes: 4 additions & 0 deletions suave/backends/eth_backends.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ func NewRemoteEthBackend(endpoint string) *RemoteEthBackend {
return r
}

func (e *RemoteEthBackend) Endpoint() string {
return e.endpoint
}

func (e *RemoteEthBackend) CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error {
if e.client == nil {
// should lock
Expand Down

0 comments on commit a5e27fb

Please sign in to comment.