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

Add verification tool for evm offchain replay #6755

Open
wants to merge 44 commits into
base: leo/add-testcase-for-offchain-evm-backward-compatibilities
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
bf4cc9a
Add new websocket handler and skeleton for its deps
illia-malachyn Nov 8, 2024
c0e50cd
Merge branch 'master' into illia-malachyn/6617-new-ws-handler
illia-malachyn Nov 8, 2024
b76c811
fix issue after merge
illia-malachyn Nov 8, 2024
f88cf9b
generate mocks. add graceful shutdown for controller
illia-malachyn Nov 8, 2024
29380d0
check err when closing conn
illia-malachyn Nov 8, 2024
b08370d
Fixed comments
illia-malachyn Nov 11, 2024
dbaa545
add additional space
illia-malachyn Nov 11, 2024
8e21498
Merge branch 'master' into illia-malachyn/6617-new-ws-handler
illia-malachyn Nov 11, 2024
b30d63d
regen data provider mock
illia-malachyn Nov 11, 2024
839c35c
rename concurrent map. add more todos for error handling
illia-malachyn Nov 14, 2024
48aaa56
Fix comments
illia-malachyn Nov 19, 2024
b42d8a7
Merge branch 'master' into illia-malachyn/6617-new-ws-handler
illia-malachyn Nov 19, 2024
006c80a
update height in comment
zhangchiqing Nov 20, 2024
c548363
Merge branch 'master' into illia-malachyn/6617-new-ws-handler
illia-malachyn Nov 20, 2024
39a777a
add offchain block context creation method
janezpodhostnik Nov 21, 2024
333cbe9
cleanup
janezpodhostnik Nov 21, 2024
f880fe7
add more details
janezpodhostnik Nov 21, 2024
501d0ff
fix init
janezpodhostnik Nov 21, 2024
d4b7668
use new block contect in offchain package
janezpodhostnik Nov 21, 2024
f4654c8
add mainnet height
janezpodhostnik Nov 21, 2024
c5bde97
Merge pull request #6743 from onflow/leo/update-height-comment
zhangchiqing Nov 21, 2024
c38f6ce
Update engine/access/rest/websockets/handler.go
peterargue Nov 21, 2024
a3676ba
Merge pull request #6630 from The-K-R-O-K/illia-malachyn/6617-new-ws-…
peterargue Nov 21, 2024
549c64f
apply review comments
janezpodhostnik Nov 22, 2024
e670c37
extract method for block hash correction
janezpodhostnik Nov 22, 2024
cadbde9
Merge branch 'master' into janez/offchain-block-context
j1010001 Nov 22, 2024
b3d0864
Merge pull request #6751 from onflow/janez/offchain-block-context
janezpodhostnik Nov 22, 2024
9bf550c
add testcase for offchain evm backward compatibilities
zhangchiqing Nov 21, 2024
27c0f3a
review comments
zhangchiqing Nov 23, 2024
2af8179
add verify evm offchain replay util cmd
zhangchiqing Nov 21, 2024
873c707
refactor serailization with gob
zhangchiqing Nov 21, 2024
7be4840
add logging
zhangchiqing Nov 21, 2024
8b08a31
update error message
zhangchiqing Nov 22, 2024
c5752a5
add register checks
zhangchiqing Nov 22, 2024
17cc1a8
store block proposal in replay
zhangchiqing Nov 22, 2024
0c7e532
fix tests
zhangchiqing Nov 22, 2024
0548f6a
update error message
zhangchiqing Nov 22, 2024
5a197b6
update error message
zhangchiqing Nov 22, 2024
af0cc4f
add account status updates
zhangchiqing Nov 22, 2024
96b7fa1
update provider
zhangchiqing Nov 23, 2024
67e32f7
update verifable keys
zhangchiqing Nov 23, 2024
c9bb7c1
update verifable keys
zhangchiqing Nov 23, 2024
f564161
skip register verification
zhangchiqing Nov 23, 2024
20487a3
fix regresion
zhangchiqing Nov 23, 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,10 @@ generate-mocks: install-mock-generators
mockery --name 'API' --dir="./engine/protocol" --case=underscore --output="./engine/protocol/mock" --outpkg="mock"
mockery --name '.*' --dir="./engine/access/state_stream" --case=underscore --output="./engine/access/state_stream/mock" --outpkg="mock"
mockery --name 'BlockTracker' --dir="./engine/access/subscription" --case=underscore --output="./engine/access/subscription/mock" --outpkg="mock"
mockery --name 'DataProvider' --dir="./engine/access/rest/websockets/data_provider" --case=underscore --output="./engine/access/rest/websockets/data_provider/mock" --outpkg="mock"
mockery --name 'ExecutionDataTracker' --dir="./engine/access/subscription" --case=underscore --output="./engine/access/subscription/mock" --outpkg="mock"
mockery --name 'ConnectionFactory' --dir="./engine/access/rpc/connection" --case=underscore --output="./engine/access/rpc/connection/mock" --outpkg="mock"
mockery --name 'Communicator' --dir="./engine/access/rpc/backend" --case=underscore --output="./engine/access/rpc/backend/mock" --outpkg="mock"

mockery --name '.*' --dir=model/fingerprint --case=underscore --output="./model/fingerprint/mock" --outpkg="mock"
mockery --name 'ExecForkActor' --structname 'ExecForkActorMock' --dir=module/mempool/consensus/mock/ --case=underscore --output="./module/mempool/consensus/mock/" --outpkg="mock"
mockery --name '.*' --dir=engine/verification/fetcher/ --case=underscore --output="./engine/verification/fetcher/mock" --outpkg="mockfetcher"
Expand Down
3 changes: 3 additions & 0 deletions cmd/observer/node_builder/observer_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
restapiproxy "github.com/onflow/flow-go/engine/access/rest/apiproxy"
commonrest "github.com/onflow/flow-go/engine/access/rest/common"
"github.com/onflow/flow-go/engine/access/rest/router"
"github.com/onflow/flow-go/engine/access/rest/websockets"
"github.com/onflow/flow-go/engine/access/rpc"
"github.com/onflow/flow-go/engine/access/rpc/backend"
rpcConnection "github.com/onflow/flow-go/engine/access/rpc/connection"
Expand Down Expand Up @@ -168,6 +169,7 @@ type ObserverServiceConfig struct {
registerCacheSize uint
programCacheSize uint
registerDBPruneThreshold uint64
websocketConfig websockets.Config
}

// DefaultObserverServiceConfig defines all the default values for the ObserverServiceConfig
Expand Down Expand Up @@ -252,6 +254,7 @@ func DefaultObserverServiceConfig() *ObserverServiceConfig {
registerCacheSize: 0,
programCacheSize: 0,
registerDBPruneThreshold: pruner.DefaultThreshold,
websocketConfig: websockets.NewDefaultWebsocketConfig(),
}
}

Expand Down
2 changes: 2 additions & 0 deletions cmd/util/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import (
"github.com/onflow/flow-go/cmd/util/cmd/snapshot"
system_addresses "github.com/onflow/flow-go/cmd/util/cmd/system-addresses"
truncate_database "github.com/onflow/flow-go/cmd/util/cmd/truncate-database"
verify_evm_offchain_replay "github.com/onflow/flow-go/cmd/util/cmd/verify-evm-offchain-replay"
"github.com/onflow/flow-go/cmd/util/cmd/version"
"github.com/onflow/flow-go/module/profiler"
)
Expand Down Expand Up @@ -126,6 +127,7 @@ func addCommands() {
rootCmd.AddCommand(debug_script.Cmd)
rootCmd.AddCommand(generate_authorization_fixes.Cmd)
rootCmd.AddCommand(evm_state_exporter.Cmd)
rootCmd.AddCommand(verify_evm_offchain_replay.Cmd)
}

func initConfig() {
Expand Down
2 changes: 2 additions & 0 deletions cmd/util/cmd/run-script/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/onflow/flow-go/cmd/util/ledger/util"
"github.com/onflow/flow-go/cmd/util/ledger/util/registers"
"github.com/onflow/flow-go/engine/access/rest"
"github.com/onflow/flow-go/engine/access/rest/websockets"
"github.com/onflow/flow-go/engine/access/state_stream/backend"
"github.com/onflow/flow-go/engine/access/subscription"
"github.com/onflow/flow-go/engine/execution/computation"
Expand Down Expand Up @@ -169,6 +170,7 @@ func run(*cobra.Command, []string) {
metrics.NewNoopCollector(),
nil,
backend.Config{},
websockets.NewDefaultWebsocketConfig(),
)
if err != nil {
log.Fatal().Err(err).Msg("failed to create server")
Expand Down
87 changes: 87 additions & 0 deletions cmd/util/cmd/verify-evm-offchain-replay/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package verify

import (
"fmt"
"strconv"
"strings"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"

"github.com/onflow/flow-go/model/flow"
)

var (
flagDatadir string
flagExecutionDataDir string
flagEVMStateGobDir string
flagChain string
flagFromTo string
)

// usage example
//
// ./util verify-evm-offchain-replay --chain flow-testnet --from-to 211176671-211177000
// --datadir /var/flow/data/protocol --execution_data_dir /var/flow/data/execution_data
var Cmd = &cobra.Command{
Use: "verify-evm-offchain-replay",
Short: "verify evm offchain replay with execution data",
Run: run,
}

func init() {
Cmd.Flags().StringVar(&flagChain, "chain", "", "Chain name")
_ = Cmd.MarkFlagRequired("chain")

Cmd.Flags().StringVar(&flagDatadir, "datadir", "/var/flow/data/protocol",
"directory that stores the protocol state")

Cmd.Flags().StringVar(&flagExecutionDataDir, "execution_data_dir", "/var/flow/data/execution_data",
"directory that stores the execution state")

Cmd.Flags().StringVar(&flagFromTo, "from_to", "",
"the flow height range to verify blocks, i.e, 1-1000, 1000-2000, 2000-3000, etc.")

Cmd.Flags().StringVar(&flagEVMStateGobDir, "evm_state_gob_dir", "/var/flow/data/evm_state_gob",
"directory that stores the evm state gob files as checkpoint")
}

func run(*cobra.Command, []string) {
_ = flow.ChainID(flagChain).Chain()

from, to, err := parseFromTo(flagFromTo)
if err != nil {
log.Fatal().Err(err).Msg("could not parse from_to")
}

log.Info().Msgf("verifying range from %d to %d", from, to)
err = Verify(log.Logger, from, to, flow.Testnet, flagDatadir, flagExecutionDataDir, flagEVMStateGobDir)
if err != nil {
log.Fatal().Err(err).Msg("could not verify height")
}
log.Info().Msgf("successfully verified range from %d to %d", from, to)

}

func parseFromTo(fromTo string) (from, to uint64, err error) {
parts := strings.Split(fromTo, "-")
if len(parts) != 2 {
return 0, 0, fmt.Errorf("invalid format: expected 'from-to', got '%s'", fromTo)
}

from, err = strconv.ParseUint(strings.TrimSpace(parts[0]), 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("invalid 'from' value: %w", err)
}

to, err = strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64)
if err != nil {
return 0, 0, fmt.Errorf("invalid 'to' value: %w", err)
}

if from > to {
return 0, 0, fmt.Errorf("'from' value (%d) must be less than or equal to 'to' value (%d)", from, to)
}

return from, to, nil
}
144 changes: 144 additions & 0 deletions cmd/util/cmd/verify-evm-offchain-replay/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package verify

import (
"fmt"
"io"
"os"
"path/filepath"

"github.com/dgraph-io/badger/v2"
badgerds "github.com/ipfs/go-ds-badger2"
"github.com/rs/zerolog"

"github.com/onflow/flow-go/cmd/util/cmd/common"
"github.com/onflow/flow-go/fvm/environment"
"github.com/onflow/flow-go/fvm/evm"
"github.com/onflow/flow-go/fvm/evm/offchain/utils"
"github.com/onflow/flow-go/fvm/evm/testutils"
"github.com/onflow/flow-go/model/flow"
"github.com/onflow/flow-go/module/blobs"
"github.com/onflow/flow-go/module/executiondatasync/execution_data"
"github.com/onflow/flow-go/storage"
)

// Verify verifies the offchain replay of EVM blocks from the given height range
// and updates the EVM state gob files with the latest state
func Verify(log zerolog.Logger, from uint64, to uint64, chainID flow.ChainID, dataDir string, executionDataDir string, evmStateGobDir string) error {
log.Info().
Str("chain", chainID.String()).
Str("dataDir", dataDir).
Str("executionDataDir", executionDataDir).
Str("evmStateGobDir", evmStateGobDir).
Msgf("verifying range from %d to %d", from, to)

db, storages, executionDataStore, dsStore, err := initStorages(chainID, dataDir, executionDataDir)
if err != nil {
return fmt.Errorf("could not initialize storages: %w", err)
}

defer db.Close()
defer dsStore.Close()

var store *testutils.TestValueStore
isRoot := isEVMRootHeight(chainID, from)
if isRoot {
log.Info().Msgf("initializing EVM state for root height %d", from)

store = testutils.GetSimpleValueStore()
as := environment.NewAccountStatus()
rootAddr := evm.StorageAccountAddress(chainID)
err = store.SetValue(rootAddr[:], []byte(flow.AccountStatusKey), as.ToBytes())
if err != nil {
return err
}
} else {
prev := from - 1
log.Info().Msgf("loading EVM state from previous height %d", prev)

valueFileName, allocatorFileName := evmStateGobFileNamesByEndHeight(evmStateGobDir, prev)
values, err := testutils.DeserializeState(valueFileName)
if err != nil {
return fmt.Errorf("could not deserialize state %v: %w", valueFileName, err)
}

allocators, err := testutils.DeserializeAllocator(allocatorFileName)
if err != nil {
return fmt.Errorf("could not deserialize allocator %v: %w", allocatorFileName, err)
}
store = testutils.GetSimpleValueStorePopulated(values, allocators)
}

err = utils.OffchainReplayBackwardCompatibilityTest(
log,
chainID,
from,
to,
storages.Headers,
storages.Results,
executionDataStore,
store,
)

if err != nil {
return err
}

valueFileName, allocatorFileName := evmStateGobFileNamesByEndHeight(evmStateGobDir, to)
values, allocators := store.Dump()
err = testutils.SerializeState(valueFileName, values)
if err != nil {
return err
}
err = testutils.SerializeAllocator(allocatorFileName, allocators)
if err != nil {
return err
}

log.Info().Msgf("saved EVM state to %s and %s", valueFileName, allocatorFileName)

return nil
}

func initStorages(chainID flow.ChainID, dataDir string, executionDataDir string) (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

chain ID is not needed

*badger.DB,
*storage.All,
execution_data.ExecutionDataGetter,
io.Closer,
error,
) {
db := common.InitStorage(dataDir)

storages := common.InitStorages(db)

datastoreDir := filepath.Join(executionDataDir, "blobstore")
err := os.MkdirAll(datastoreDir, 0700)
if err != nil {
return nil, nil, nil, nil, err
}
dsOpts := &badgerds.DefaultOptions
ds, err := badgerds.NewDatastore(datastoreDir, dsOpts)
if err != nil {
return nil, nil, nil, nil, err
}

executionDataBlobstore := blobs.NewBlobstore(ds)
executionDataStore := execution_data.NewExecutionDataStore(executionDataBlobstore, execution_data.DefaultSerializer)

return db, storages, executionDataStore, ds, nil
}

// EVM Root Height is the first block that has EVM Block Event where the EVM block height is 1
func isEVMRootHeight(chainID flow.ChainID, flowHeight uint64) bool {
if chainID == flow.Testnet {
return flowHeight == 211176671
} else if chainID == flow.Mainnet {
return flowHeight == 85981136
}
return flowHeight == 1
}

func evmStateGobFileNamesByEndHeight(evmStateGobDir string, endHeight uint64) (string, string) {
valueFileName := filepath.Join(evmStateGobDir, fmt.Sprintf("values-%d.gob", endHeight))
allocatorFileName := filepath.Join(evmStateGobDir, fmt.Sprintf("allocators-%d.gob", endHeight))
return valueFileName, allocatorFileName
}
2 changes: 2 additions & 0 deletions engine/access/handle_irrecoverable_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
accessmock "github.com/onflow/flow-go/engine/access/mock"
"github.com/onflow/flow-go/engine/access/rest"
"github.com/onflow/flow-go/engine/access/rest/router"
"github.com/onflow/flow-go/engine/access/rest/websockets"
"github.com/onflow/flow-go/engine/access/rpc"
"github.com/onflow/flow-go/engine/access/rpc/backend"
statestreambackend "github.com/onflow/flow-go/engine/access/state_stream/backend"
Expand Down Expand Up @@ -109,6 +110,7 @@ func (suite *IrrecoverableStateTestSuite) SetupTest() {
RestConfig: rest.Config{
ListenAddress: unittest.DefaultAddress,
},
WebSocketConfig: websockets.NewDefaultWebsocketConfig(),
}

// generate a server certificate that will be served by the GRPC server
Expand Down
2 changes: 2 additions & 0 deletions engine/access/integration_unsecure_grpc_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/onflow/flow-go/engine"
"github.com/onflow/flow-go/engine/access/index"
accessmock "github.com/onflow/flow-go/engine/access/mock"
"github.com/onflow/flow-go/engine/access/rest/websockets"
"github.com/onflow/flow-go/engine/access/rpc"
"github.com/onflow/flow-go/engine/access/rpc/backend"
"github.com/onflow/flow-go/engine/access/state_stream"
Expand Down Expand Up @@ -138,6 +139,7 @@ func (suite *SameGRPCPortTestSuite) SetupTest() {
UnsecureGRPCListenAddr: unittest.DefaultAddress,
SecureGRPCListenAddr: unittest.DefaultAddress,
HTTPListenAddr: unittest.DefaultAddress,
WebSocketConfig: websockets.NewDefaultWebsocketConfig(),
}

blockCount := 5
Expand Down
27 changes: 23 additions & 4 deletions engine/access/rest/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package router

import (
"fmt"
"net/http"
"regexp"
"strings"

Expand All @@ -10,8 +11,9 @@ import (

"github.com/onflow/flow-go/access"
"github.com/onflow/flow-go/engine/access/rest/common/middleware"
"github.com/onflow/flow-go/engine/access/rest/http"
flowhttp "github.com/onflow/flow-go/engine/access/rest/http"
"github.com/onflow/flow-go/engine/access/rest/http/models"
"github.com/onflow/flow-go/engine/access/rest/websockets"
legacyws "github.com/onflow/flow-go/engine/access/rest/websockets/legacy"
"github.com/onflow/flow-go/engine/access/state_stream"
"github.com/onflow/flow-go/engine/access/state_stream/backend"
Expand Down Expand Up @@ -54,7 +56,7 @@ func (b *RouterBuilder) AddRestRoutes(
) *RouterBuilder {
linkGenerator := models.NewLinkGeneratorImpl(b.v1SubRouter)
for _, r := range Routes {
h := http.NewHandler(b.logger, backend, r.Handler, linkGenerator, chain, maxRequestSize)
h := flowhttp.NewHandler(b.logger, backend, r.Handler, linkGenerator, chain, maxRequestSize)
b.v1SubRouter.
Methods(r.Method).
Path(r.Pattern).
Expand All @@ -64,8 +66,8 @@ func (b *RouterBuilder) AddRestRoutes(
return b
}

// AddWsLegacyRoutes adds WebSocket routes to the router.
func (b *RouterBuilder) AddWsLegacyRoutes(
// AddLegacyWebsocketsRoutes adds WebSocket routes to the router.
func (b *RouterBuilder) AddLegacyWebsocketsRoutes(
stateStreamApi state_stream.API,
chain flow.Chain,
stateStreamConfig backend.Config,
Expand All @@ -84,6 +86,23 @@ func (b *RouterBuilder) AddWsLegacyRoutes(
return b
}

func (b *RouterBuilder) AddWebsocketsRoute(
chain flow.Chain,
config websockets.Config,
streamApi state_stream.API,
streamConfig backend.Config,
maxRequestSize int64,
) *RouterBuilder {
handler := websockets.NewWebSocketHandler(b.logger, config, chain, streamApi, streamConfig, maxRequestSize)
b.v1SubRouter.
Methods(http.MethodGet).
Path("/ws").
Name("ws").
Handler(handler)

return b
}

func (b *RouterBuilder) Build() *mux.Router {
return b.router
}
Expand Down
Loading