diff --git a/cmd/export/cmd.go b/cmd/export/cmd.go new file mode 100644 index 00000000..3a56128d --- /dev/null +++ b/cmd/export/cmd.go @@ -0,0 +1,80 @@ +package export + +import ( + "fmt" + + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + + "github.com/onflow/flow-evm-gateway/storage/pebble" + "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/offchain/storage" + flowGo "github.com/onflow/flow-go/model/flow" +) + +var Cmd = &cobra.Command{ + Use: "export-evm-state", + Short: "Export EVM state at a specific height", + RunE: func(*cobra.Command, []string) error { + if height == 0 || outputDir == "" || registerStoreDir == "" { + return fmt.Errorf("all flags (height, output, register-store) must be provided") + } + + log.Info().Msgf("exporting EVM state for height %v from registerStoreDir %v, outputDir: %v, chain: %v", height, registerStoreDir, outputDir, chain) + + chainID := flowGo.ChainID(chain) + + err := ExportEVMStateForHeight(height, outputDir, registerStoreDir, chainID) + if err != nil { + return fmt.Errorf("fail to export: %w", err) + } + + log.Info().Msgf("successfully exported EVM state to %v", outputDir) + + return nil + }, +} + +var ( + height uint64 + outputDir string + chain string + registerStoreDir string +) + +func init() { + Cmd.Flags().Uint64Var(&height, "evm-height", 0, "EVM Block height for EVM state export") + Cmd.Flags().StringVar(&outputDir, "output", "", "Output directory for exported EVM state") + Cmd.Flags().StringVar(&chain, "chain-id", "testnet", "Chain ID for the EVM state") + Cmd.Flags().StringVar(®isterStoreDir, "register-store", "", "Directory of the register store") +} + +func ExportEVMStateForHeight(height uint64, outputDir string, registerStoreDir string, chainID flowGo.ChainID) error { + storageAddress := evm.StorageAccountAddress(chainID) + + pebbleDB, err := pebble.OpenDB(registerStoreDir) + if err != nil { + return fmt.Errorf("failed to open pebble db: %w", err) + } + + store := pebble.New(pebbleDB, log.Logger) + registerStore := pebble.NewRegisterStorage(store, storageAddress) + snapshot, err := registerStore.GetSnapshotAt(height) + if err != nil { + return err + } + + ledger := storage.NewReadOnlyStorage(snapshot) + exporter, err := state.NewExporter(ledger, storageAddress) + if err != nil { + return err + } + + err = exporter.ExportGob(outputDir) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/main.go b/cmd/main.go index 68c22a0b..7a90a575 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,6 +3,7 @@ package main import ( "os" + "github.com/onflow/flow-evm-gateway/cmd/export" "github.com/onflow/flow-evm-gateway/cmd/run" "github.com/onflow/flow-evm-gateway/cmd/version" "github.com/rs/zerolog/log" @@ -23,6 +24,7 @@ func Execute() { func main() { rootCmd.AddCommand(version.Cmd) + rootCmd.AddCommand(export.Cmd) rootCmd.AddCommand(run.Cmd) Execute() diff --git a/go.mod b/go.mod index 42016a60..af84aab9 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( github.com/hashicorp/go-multierror v1.1.1 github.com/onflow/atree v0.8.0 github.com/onflow/cadence v1.2.2 - github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1 + github.com/onflow/flow-go v0.38.0-preview.0.4 github.com/onflow/flow-go-sdk v1.2.3 github.com/onflow/go-ethereum v1.14.7 github.com/prometheus/client_golang v1.18.0 diff --git a/go.sum b/go.sum index 50db1a3a..f4226b2e 100644 --- a/go.sum +++ b/go.sum @@ -535,8 +535,8 @@ github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3 github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1 h1:tE21Kgx2Aqll9ywbiRDfc2BVIz5g6zKdrIom9U9eTE4= -github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= +github.com/onflow/flow-go v0.38.0-preview.0.4 h1:vjnp6btehu3X/aYjsXYlA3r/GGYeB05so0d7ICtXbmg= +github.com/onflow/flow-go v0.38.0-preview.0.4/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= github.com/onflow/flow-go-sdk v1.2.3 h1:jb+0dIXBO12Zt8x3c2xDXYPv6k3sRTUvhe59M+EcXTI= github.com/onflow/flow-go-sdk v1.2.3/go.mod h1:jMaffBTlAIdutx+pBhRIigLZFIBYSDDST0Uax1rW2qo= github.com/onflow/flow-nft/lib/go/contracts v1.2.2 h1:XFERNVUDGbZ4ViZjt7P1cGD80mO1PzUJYPfdhXFsGbQ= diff --git a/services/evm/extract.go b/services/evm/extract.go new file mode 100644 index 00000000..e37cb1f8 --- /dev/null +++ b/services/evm/extract.go @@ -0,0 +1,36 @@ +package evm + +import ( + "fmt" + + "github.com/onflow/flow-evm-gateway/storage/pebble" + "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/evm/emulator/state" + "github.com/onflow/flow-go/fvm/evm/offchain/storage" + flowGo "github.com/onflow/flow-go/model/flow" +) + +func ExtractEVMState( + chainID flowGo.ChainID, + evmHeight uint64, + store *pebble.Storage, +) (*state.EVMState, error) { + storageRoot := evm.StorageAccountAddress(chainID) + registerStore := pebble.NewRegisterStorage(store, storageRoot) + snapshot, err := registerStore.GetSnapshotAt(evmHeight) + if err != nil { + return nil, fmt.Errorf("failed to get snapshot at evm height %d: %w", evmHeight, err) + } + + ledger := storage.NewReadOnlyStorage(snapshot) + bv, err := state.NewBaseView(ledger, storageRoot) + if err != nil { + return nil, fmt.Errorf("failed to create base view: %w", err) + } + + evmState, err := state.Extract(storageRoot, bv) + if err != nil { + return nil, err + } + return evmState, nil +} diff --git a/services/evm/extract_test.go b/services/evm/extract_test.go new file mode 100644 index 00000000..f4bca319 --- /dev/null +++ b/services/evm/extract_test.go @@ -0,0 +1,46 @@ +package evm_test + +import ( + "fmt" + "testing" + + "github.com/onflow/flow-evm-gateway/storage/pebble" + "github.com/onflow/flow-go/fvm/evm/emulator/state" + flowGo "github.com/onflow/flow-go/model/flow" + "github.com/rs/zerolog/log" + "github.com/stretchr/testify/require" + + evmState "github.com/onflow/flow-evm-gateway/services/evm" +) + +func StateDiff(t *testing.T) { + state1 := extractEVMState(t, flowGo.Testnet, "/var/flow52/evm/data/db", uint64(17724990)) + state2 := evmStateFromCheckpointExtract(t, "/var/flow52/evm-state-from-checkpoint-228901661") + + differences := state.Diff(state1, state2) + + for i, diff := range differences { + fmt.Printf("Difference %d: %v\n", i, diff) + } + + require.Len(t, differences, 0) +} + +func extractEVMState( + t *testing.T, chainID flowGo.ChainID, + registerStoreDir string, evmHeight uint64) *state.EVMState { + + pebbleDB, err := pebble.OpenDB(registerStoreDir) + require.NoError(t, err) + store := pebble.New(pebbleDB, log.Logger) + + evmState, err := evmState.ExtractEVMState(chainID, evmHeight, store) + require.NoError(t, err) + return evmState +} + +func evmStateFromCheckpointExtract(t *testing.T, dir string) *state.EVMState { + enState, err := state.ImportEVMStateFromGob(dir) + require.NoError(t, err) + return enState +} diff --git a/tests/go.mod b/tests/go.mod index 742ebb54..65b9c20e 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -8,7 +8,7 @@ require ( github.com/onflow/crypto v0.25.2 github.com/onflow/flow-emulator v1.1.1-0.20241125195348-4e121ffb12af github.com/onflow/flow-evm-gateway v0.0.0-20240201154855-4d4d3d3f19c7 - github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1 + github.com/onflow/flow-go v0.38.0-preview.0.4 github.com/onflow/flow-go-sdk v1.2.3 github.com/onflow/go-ethereum v1.14.7 github.com/rs/zerolog v1.33.0 diff --git a/tests/go.sum b/tests/go.sum index e41b13ee..fc082fd7 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -799,8 +799,8 @@ github.com/onflow/flow-ft/lib/go/contracts v1.0.1 h1:Ts5ob+CoCY2EjEd0W6vdLJ7hLL3 github.com/onflow/flow-ft/lib/go/contracts v1.0.1/go.mod h1:PwsL8fC81cjnUnTfmyL/HOIyHnyaw/JA474Wfj2tl6A= github.com/onflow/flow-ft/lib/go/templates v1.0.1 h1:FDYKAiGowABtoMNusLuRCILIZDtVqJ/5tYI4VkF5zfM= github.com/onflow/flow-ft/lib/go/templates v1.0.1/go.mod h1:uQ8XFqmMK2jxyBSVrmyuwdWjTEb+6zGjRYotfDJ5pAE= -github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1 h1:tE21Kgx2Aqll9ywbiRDfc2BVIz5g6zKdrIom9U9eTE4= -github.com/onflow/flow-go v0.38.0-preview.0.0.20241125190444-25a8af57bea1/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= +github.com/onflow/flow-go v0.38.0-preview.0.4 h1:vjnp6btehu3X/aYjsXYlA3r/GGYeB05so0d7ICtXbmg= +github.com/onflow/flow-go v0.38.0-preview.0.4/go.mod h1:c4ubAQ2WIMYY/TOaBvbajROEFWv2HwhKeGOsEdLPIM0= github.com/onflow/flow-go-sdk v1.2.3 h1:jb+0dIXBO12Zt8x3c2xDXYPv6k3sRTUvhe59M+EcXTI= github.com/onflow/flow-go-sdk v1.2.3/go.mod h1:jMaffBTlAIdutx+pBhRIigLZFIBYSDDST0Uax1rW2qo= github.com/onflow/flow-nft/lib/go/contracts v1.2.2 h1:XFERNVUDGbZ4ViZjt7P1cGD80mO1PzUJYPfdhXFsGbQ=