Skip to content

Commit 85913ad

Browse files
authored
Merge pull request #6755 from onflow/leo/add-verify-evm-offchain-replay
Add verification tool for evm offchain replay
2 parents c96b754 + 1bdc486 commit 85913ad

File tree

13 files changed

+926
-182
lines changed

13 files changed

+926
-182
lines changed

cmd/util/cmd/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import (
4141
"github.com/onflow/flow-go/cmd/util/cmd/snapshot"
4242
system_addresses "github.com/onflow/flow-go/cmd/util/cmd/system-addresses"
4343
truncate_database "github.com/onflow/flow-go/cmd/util/cmd/truncate-database"
44+
verify_evm_offchain_replay "github.com/onflow/flow-go/cmd/util/cmd/verify-evm-offchain-replay"
4445
verify_execution_result "github.com/onflow/flow-go/cmd/util/cmd/verify_execution_result"
4546
"github.com/onflow/flow-go/cmd/util/cmd/version"
4647
"github.com/onflow/flow-go/module/profiler"
@@ -128,6 +129,7 @@ func addCommands() {
128129
rootCmd.AddCommand(generate_authorization_fixes.Cmd)
129130
rootCmd.AddCommand(evm_state_exporter.Cmd)
130131
rootCmd.AddCommand(verify_execution_result.Cmd)
132+
rootCmd.AddCommand(verify_evm_offchain_replay.Cmd)
131133
}
132134

133135
func initConfig() {
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package verify
2+
3+
import (
4+
"fmt"
5+
"strconv"
6+
"strings"
7+
8+
"github.com/rs/zerolog/log"
9+
"github.com/spf13/cobra"
10+
11+
"github.com/onflow/flow-go/model/flow"
12+
)
13+
14+
var (
15+
flagDatadir string
16+
flagExecutionDataDir string
17+
flagEVMStateGobDir string
18+
flagChain string
19+
flagFromTo string
20+
flagSaveEveryNBlocks uint64
21+
)
22+
23+
// usage example
24+
//
25+
// ./util verify-evm-offchain-replay --chain flow-testnet --from_to 211176670-211177000
26+
// --datadir /var/flow/data/protocol --execution_data_dir /var/flow/data/execution_data
27+
var Cmd = &cobra.Command{
28+
Use: "verify-evm-offchain-replay",
29+
Short: "verify evm offchain replay with execution data",
30+
Run: run,
31+
}
32+
33+
func init() {
34+
Cmd.Flags().StringVar(&flagChain, "chain", "", "Chain name")
35+
_ = Cmd.MarkFlagRequired("chain")
36+
37+
Cmd.Flags().StringVar(&flagDatadir, "datadir", "/var/flow/data/protocol",
38+
"directory that stores the protocol state")
39+
40+
Cmd.Flags().StringVar(&flagExecutionDataDir, "execution_data_dir", "/var/flow/data/execution_data",
41+
"directory that stores the execution state")
42+
43+
Cmd.Flags().StringVar(&flagFromTo, "from_to", "",
44+
"the flow height range to verify blocks, i.e, 1-1000, 1000-2000, 2000-3000, etc.")
45+
46+
Cmd.Flags().StringVar(&flagEVMStateGobDir, "evm_state_gob_dir", "/var/flow/data/evm_state_gob",
47+
"directory that stores the evm state gob files as checkpoint")
48+
49+
Cmd.Flags().Uint64Var(&flagSaveEveryNBlocks, "save_every", uint64(1_000_000),
50+
"save the evm state gob files every N blocks")
51+
}
52+
53+
func run(*cobra.Command, []string) {
54+
chainID := flow.ChainID(flagChain)
55+
56+
from, to, err := parseFromTo(flagFromTo)
57+
if err != nil {
58+
log.Fatal().Err(err).Msg("could not parse from_to")
59+
}
60+
61+
err = Verify(log.Logger, from, to, chainID, flagDatadir, flagExecutionDataDir, flagEVMStateGobDir, flagSaveEveryNBlocks)
62+
if err != nil {
63+
log.Fatal().Err(err).Msg("could not verify height")
64+
}
65+
}
66+
67+
func parseFromTo(fromTo string) (from, to uint64, err error) {
68+
parts := strings.Split(fromTo, "-")
69+
if len(parts) != 2 {
70+
return 0, 0, fmt.Errorf("invalid format: expected 'from-to', got '%s'", fromTo)
71+
}
72+
73+
from, err = strconv.ParseUint(strings.TrimSpace(parts[0]), 10, 64)
74+
if err != nil {
75+
return 0, 0, fmt.Errorf("invalid 'from' value: %w", err)
76+
}
77+
78+
to, err = strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64)
79+
if err != nil {
80+
return 0, 0, fmt.Errorf("invalid 'to' value: %w", err)
81+
}
82+
83+
if from > to {
84+
return 0, 0, fmt.Errorf("'from' value (%d) must be less than or equal to 'to' value (%d)", from, to)
85+
}
86+
87+
return from, to, nil
88+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
package verify
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/dgraph-io/badger/v2"
10+
badgerds "github.com/ipfs/go-ds-badger2"
11+
"github.com/rs/zerolog"
12+
"github.com/rs/zerolog/log"
13+
14+
"github.com/onflow/flow-go/cmd/util/cmd/common"
15+
"github.com/onflow/flow-go/fvm/evm/offchain/utils"
16+
"github.com/onflow/flow-go/fvm/evm/testutils"
17+
"github.com/onflow/flow-go/model/flow"
18+
"github.com/onflow/flow-go/module/blobs"
19+
"github.com/onflow/flow-go/module/executiondatasync/execution_data"
20+
"github.com/onflow/flow-go/storage"
21+
)
22+
23+
// Verify verifies the offchain replay of EVM blocks from the given height range
24+
// and updates the EVM state gob files with the latest state
25+
func Verify(
26+
log zerolog.Logger,
27+
from uint64,
28+
to uint64,
29+
chainID flow.ChainID,
30+
dataDir string,
31+
executionDataDir string,
32+
evmStateGobDir string,
33+
saveEveryNBlocks uint64,
34+
) error {
35+
lg := log.With().
36+
Uint64("from", from).Uint64("to", to).
37+
Str("chain", chainID.String()).
38+
Str("dataDir", dataDir).
39+
Str("executionDataDir", executionDataDir).
40+
Str("evmStateGobDir", evmStateGobDir).
41+
Uint64("saveEveryNBlocks", saveEveryNBlocks).
42+
Logger()
43+
44+
lg.Info().Msgf("verifying range from %d to %d", from, to)
45+
46+
db, storages, executionDataStore, dsStore, err := initStorages(dataDir, executionDataDir)
47+
if err != nil {
48+
return fmt.Errorf("could not initialize storages: %w", err)
49+
}
50+
51+
defer db.Close()
52+
defer dsStore.Close()
53+
54+
var store *testutils.TestValueStore
55+
56+
// root block require the account status registers to be saved
57+
isRoot := utils.IsEVMRootHeight(chainID, from)
58+
if isRoot {
59+
store = testutils.GetSimpleValueStore()
60+
} else {
61+
prev := from - 1
62+
store, err = loadState(prev, evmStateGobDir)
63+
if err != nil {
64+
return fmt.Errorf("could not load EVM state from previous height %d: %w", prev, err)
65+
}
66+
}
67+
68+
// save state every N blocks
69+
onHeightReplayed := func(height uint64) error {
70+
log.Info().Msgf("replayed height %d", height)
71+
if height%saveEveryNBlocks == 0 {
72+
err := saveState(store, height, evmStateGobDir)
73+
if err != nil {
74+
return err
75+
}
76+
}
77+
return nil
78+
}
79+
80+
// replay blocks
81+
err = utils.OffchainReplayBackwardCompatibilityTest(
82+
log,
83+
chainID,
84+
from,
85+
to,
86+
storages.Headers,
87+
storages.Results,
88+
executionDataStore,
89+
store,
90+
onHeightReplayed,
91+
)
92+
93+
if err != nil {
94+
return err
95+
}
96+
97+
err = saveState(store, to, evmStateGobDir)
98+
if err != nil {
99+
return err
100+
}
101+
102+
lg.Info().Msgf("successfully verified range from %d to %d", from, to)
103+
104+
return nil
105+
}
106+
107+
func saveState(store *testutils.TestValueStore, height uint64, gobDir string) error {
108+
valueFileName, allocatorFileName := evmStateGobFileNamesByEndHeight(gobDir, height)
109+
values, allocators := store.Dump()
110+
err := testutils.SerializeState(valueFileName, values)
111+
if err != nil {
112+
return err
113+
}
114+
err = testutils.SerializeAllocator(allocatorFileName, allocators)
115+
if err != nil {
116+
return err
117+
}
118+
119+
log.Info().Msgf("saved EVM state to %s and %s", valueFileName, allocatorFileName)
120+
121+
return nil
122+
}
123+
124+
func loadState(height uint64, gobDir string) (*testutils.TestValueStore, error) {
125+
valueFileName, allocatorFileName := evmStateGobFileNamesByEndHeight(gobDir, height)
126+
values, err := testutils.DeserializeState(valueFileName)
127+
if err != nil {
128+
return nil, fmt.Errorf("could not deserialize state %v: %w", valueFileName, err)
129+
}
130+
131+
allocators, err := testutils.DeserializeAllocator(allocatorFileName)
132+
if err != nil {
133+
return nil, fmt.Errorf("could not deserialize allocator %v: %w", allocatorFileName, err)
134+
}
135+
store := testutils.GetSimpleValueStorePopulated(values, allocators)
136+
137+
log.Info().Msgf("loaded EVM state for height %d from gob file %v", height, valueFileName)
138+
return store, nil
139+
}
140+
141+
func initStorages(dataDir string, executionDataDir string) (
142+
*badger.DB,
143+
*storage.All,
144+
execution_data.ExecutionDataGetter,
145+
io.Closer,
146+
error,
147+
) {
148+
db := common.InitStorage(dataDir)
149+
150+
storages := common.InitStorages(db)
151+
152+
datastoreDir := filepath.Join(executionDataDir, "blobstore")
153+
err := os.MkdirAll(datastoreDir, 0700)
154+
if err != nil {
155+
return nil, nil, nil, nil, err
156+
}
157+
dsOpts := &badgerds.DefaultOptions
158+
ds, err := badgerds.NewDatastore(datastoreDir, dsOpts)
159+
if err != nil {
160+
return nil, nil, nil, nil, err
161+
}
162+
163+
executionDataBlobstore := blobs.NewBlobstore(ds)
164+
executionDataStore := execution_data.NewExecutionDataStore(executionDataBlobstore, execution_data.DefaultSerializer)
165+
166+
return db, storages, executionDataStore, ds, nil
167+
}
168+
169+
func evmStateGobFileNamesByEndHeight(evmStateGobDir string, endHeight uint64) (string, string) {
170+
valueFileName := filepath.Join(evmStateGobDir, fmt.Sprintf("values-%d.gob", endHeight))
171+
allocatorFileName := filepath.Join(evmStateGobDir, fmt.Sprintf("allocators-%d.gob", endHeight))
172+
return valueFileName, allocatorFileName
173+
}

fvm/evm/handler/blockHashList.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package handler
33
import (
44
"encoding/binary"
55
"fmt"
6+
"strings"
67

78
gethCommon "github.com/onflow/go-ethereum/common"
89

@@ -26,6 +27,14 @@ const (
2627
heightEncodingSize
2728
)
2829

30+
func IsBlockHashListBucketKeyFormat(id flow.RegisterID) bool {
31+
return strings.HasPrefix(id.Key, "BlockHashListBucket")
32+
}
33+
34+
func IsBlockHashListMetaKey(id flow.RegisterID) bool {
35+
return id.Key == blockHashListMetaKey
36+
}
37+
2938
// BlockHashList stores the last `capacity` number of block hashes
3039
//
3140
// Under the hood it breaks the list of hashes into
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package blocks
2+
3+
import (
4+
"github.com/onflow/flow-go/fvm/evm/events"
5+
"github.com/onflow/flow-go/fvm/evm/types"
6+
)
7+
8+
func ReconstructProposal(
9+
blockEvent *events.BlockEventPayload,
10+
results []*types.Result,
11+
) *types.BlockProposal {
12+
receipts := make([]types.LightReceipt, 0, len(results))
13+
txHashes := make(types.TransactionHashes, 0, len(results))
14+
15+
for _, result := range results {
16+
receipts = append(receipts, *result.LightReceipt())
17+
txHashes = append(txHashes, result.TxHash)
18+
}
19+
20+
return &types.BlockProposal{
21+
Block: types.Block{
22+
ParentBlockHash: blockEvent.ParentBlockHash,
23+
Height: blockEvent.Height,
24+
Timestamp: blockEvent.Timestamp,
25+
TotalSupply: blockEvent.TotalSupply.Big(),
26+
ReceiptRoot: blockEvent.ReceiptRoot,
27+
TransactionHashRoot: blockEvent.TransactionHashRoot,
28+
TotalGasUsed: blockEvent.TotalGasUsed,
29+
PrevRandao: blockEvent.PrevRandao,
30+
},
31+
Receipts: receipts,
32+
TxHashes: txHashes,
33+
}
34+
}

0 commit comments

Comments
 (0)