diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 16e4606d5e2..03b0782e621 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,7 +49,7 @@ jobs: uses: golangci/golangci-lint-action@v3 with: # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. - version: v1.54 + version: v1.63 args: -v working-directory: ${{ matrix.dir }} # https://github.com/golangci/golangci-lint-action/issues/244 diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index bfffe887b3f..4c209b15693 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -63,6 +63,7 @@ import ( "github.com/onflow/flow-go/engine/common/stop" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/engine/common/version" + "github.com/onflow/flow-go/engine/execution/computation" "github.com/onflow/flow-go/engine/execution/computation/query" "github.com/onflow/flow-go/fvm/storage/derived" "github.com/onflow/flow-go/ledger" @@ -991,7 +992,7 @@ func (builder *FlowAccessNodeBuilder) BuildExecutionSyncComponents() *FlowAccess builder.Logger, metrics.NewExecutionCollector(builder.Tracer), builder.RootChainID, - query.NewProtocolStateWrapper(builder.State), + computation.NewProtocolStateWrapper(builder.State), builder.Storage.Headers, builder.ExecutionIndexerCore.RegisterValue, builder.scriptExecutorConfig, diff --git a/cmd/execution_builder.go b/cmd/execution_builder.go index 3d506a70ced..72a7dc62b99 100644 --- a/cmd/execution_builder.go +++ b/cmd/execution_builder.go @@ -279,7 +279,7 @@ func (exeNode *ExecutionNode) LoadExecutionMetrics(node *NodeConfig) error { // the root block as executed block var height uint64 var blockID flow.Identifier - err := node.DB.View(procedure.GetHighestExecutedBlock(&height, &blockID)) + err := node.DB.View(procedure.GetLastExecutedBlock(&height, &blockID)) if err != nil { // database has not been bootstrapped yet if errors.Is(err, storageerr.ErrNotFound) { @@ -551,7 +551,7 @@ func (exeNode *ExecutionNode) LoadProviderEngine( collector, node.Tracer, node.Me, - node.State, + computation.NewProtocolStateWrapper(node.State), vmCtx, ledgerViewCommitter, executionDataProvider, @@ -590,7 +590,7 @@ func (exeNode *ExecutionNode) LoadProviderEngine( // Get latest executed block and a view at that block ctx := context.Background() - height, blockID, err := exeNode.executionState.GetHighestExecutedBlockID(ctx) + height, blockID, err := exeNode.executionState.GetLastExecutedBlockID(ctx) if err != nil { return nil, fmt.Errorf( "cannot get the latest executed block id at height %v: %w", @@ -762,12 +762,12 @@ func (exeNode *ExecutionNode) LoadExecutionState( exeNode.exeConf.enableStorehouse, ) - height, _, err := exeNode.executionState.GetHighestExecutedBlockID(context.Background()) + height, _, err := exeNode.executionState.GetLastExecutedBlockID(context.Background()) if err != nil { - return nil, fmt.Errorf("could not get highest executed block: %w", err) + return nil, fmt.Errorf("could not get last executed block: %w", err) } - log.Info().Msgf("execution state highest executed block height: %v", height) + log.Info().Msgf("execution state last executed block height: %v", height) exeNode.collector.ExecutionLastExecutedBlockHeight(height) return &module.NoopReadyDoneAware{}, nil diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index ea24d064262..6829b115060 100644 --- a/cmd/observer/node_builder/observer_builder.go +++ b/cmd/observer/node_builder/observer_builder.go @@ -57,6 +57,7 @@ import ( "github.com/onflow/flow-go/engine/common/stop" synceng "github.com/onflow/flow-go/engine/common/synchronization" "github.com/onflow/flow-go/engine/common/version" + "github.com/onflow/flow-go/engine/execution/computation" "github.com/onflow/flow-go/engine/execution/computation/query" "github.com/onflow/flow-go/fvm/storage/derived" "github.com/onflow/flow-go/ledger" @@ -1454,7 +1455,7 @@ func (builder *ObserverServiceBuilder) BuildExecutionSyncComponents() *ObserverS builder.Logger, metrics.NewExecutionCollector(builder.Tracer), builder.RootChainID, - query.NewProtocolStateWrapper(builder.State), + computation.NewProtocolStateWrapper(builder.State), builder.Storage.Headers, builder.ExecutionIndexerCore.RegisterValue, builder.scriptExecutorConfig, diff --git a/cmd/util/cmd/common/node_info.go b/cmd/util/cmd/common/node_info.go index 9263e5c96a3..33522aa70a9 100644 --- a/cmd/util/cmd/common/node_info.go +++ b/cmd/util/cmd/common/node_info.go @@ -41,16 +41,16 @@ func ReadFullPartnerNodeInfos(log zerolog.Logger, partnerWeightsPath, partnerNod } err = ValidateNetworkPubKey(partner.NetworkPubKey) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid network public key: %s", partner.NetworkPubKey)) + return nil, fmt.Errorf("invalid network public key: %s", partner.NetworkPubKey) } err = ValidateStakingPubKey(partner.StakingPubKey) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid staking public key: %s", partner.StakingPubKey)) + return nil, fmt.Errorf("invalid staking public key: %s", partner.StakingPubKey) } weight := weights[partner.NodeID] if valid := ValidateWeight(weight); !valid { - return nil, fmt.Errorf(fmt.Sprintf("invalid partner weight %v: %d", partner.NodeID, weight)) + return nil, fmt.Errorf("invalid partner weight %v: %d", partner.NodeID, weight) } if weight != flow.DefaultInitialWeight { @@ -143,12 +143,12 @@ func ReadFullInternalNodeInfos(log zerolog.Logger, internalNodePrivInfoDir, inte // validate every single internal node err := ValidateNodeID(internal.NodeID) if err != nil { - return nil, fmt.Errorf(fmt.Sprintf("invalid internal node ID: %s", internal.NodeID)) + return nil, fmt.Errorf("invalid internal node ID: %s", internal.NodeID) } weight := weights[internal.Address] if valid := ValidateWeight(weight); !valid { - return nil, fmt.Errorf(fmt.Sprintf("invalid partner weight %v: %d", internal.NodeID, weight)) + return nil, fmt.Errorf("invalid partner weight %v: %d", internal.NodeID, weight) } if weight != flow.DefaultInitialWeight { log.Warn().Msgf("internal node (id=%x) has non-default weight (%d != %d)", internal.NodeID, weight, flow.DefaultInitialWeight) diff --git a/cmd/util/cmd/find-inconsistent-result/cmd.go b/cmd/util/cmd/find-inconsistent-result/cmd.go index 4ea0e69037b..7a2ceea5036 100644 --- a/cmd/util/cmd/find-inconsistent-result/cmd.go +++ b/cmd/util/cmd/find-inconsistent-result/cmd.go @@ -180,7 +180,7 @@ func findLastExecutedAndSealedHeight(state protocol.State, db *badger.DB) (uint6 var blockID flow.Identifier var lastExecuted uint64 - err = db.View(procedure.GetHighestExecutedBlock(&lastExecuted, &blockID)) + err = db.View(procedure.GetLastExecutedBlock(&lastExecuted, &blockID)) if err != nil { return 0, err } diff --git a/cmd/util/cmd/verify_execution_result/cmd.go b/cmd/util/cmd/verify_execution_result/cmd.go index 9263773aa5b..91ddd33ec0e 100644 --- a/cmd/util/cmd/verify_execution_result/cmd.go +++ b/cmd/util/cmd/verify_execution_result/cmd.go @@ -24,7 +24,7 @@ var ( // # verify the last 100 sealed blocks // ./util verify_execution_result --chain flow-testnet --datadir /var/flow/data/protocol --chunk_data_pack_dir /var/flow/data/chunk_data_pack --lastk 100 // # verify the blocks from height 2000 to 3000 -// ./util verify_execution_result --chain flow-testnet --datadir /var/flow/data/protocol --chunk_data_pack_dir /var/flow/data/chunk_data_pack --from_to 2000-3000 +// ./util verify_execution_result --chain flow-testnet --datadir /var/flow/data/protocol --chunk_data_pack_dir /var/flow/data/chunk_data_pack --from_to 2000_3000 var Cmd = &cobra.Command{ Use: "verify-execution-result", Short: "verify block execution by verifying all chunks in the result", @@ -47,7 +47,7 @@ func init() { "last k sealed blocks to verify") Cmd.Flags().StringVar(&flagFromTo, "from_to", "", - "the height range to verify blocks (inclusive), i.e, 1-1000, 1000-2000, 2000-3000, etc.") + "the height range to verify blocks (inclusive), i.e, 1_1000, 1000_2000, 2000_3000, etc.") Cmd.Flags().UintVar(&flagWorkerCount, "worker_count", 1, "number of workers to use for verification, default is 1") @@ -93,9 +93,9 @@ func run(*cobra.Command, []string) { } func parseFromTo(fromTo string) (from, to uint64, err error) { - parts := strings.Split(fromTo, "-") + parts := strings.Split(fromTo, "_") if len(parts) != 2 { - return 0, 0, fmt.Errorf("invalid format: expected 'from-to', got '%s'", fromTo) + return 0, 0, fmt.Errorf("invalid format: expected 'from_to', got '%s'", fromTo) } from, err = strconv.ParseUint(strings.TrimSpace(parts[0]), 10, 64) diff --git a/engine/access/rpc/backend/backend_transactions.go b/engine/access/rpc/backend/backend_transactions.go index 5ba1cecb038..9481d217f08 100644 --- a/engine/access/rpc/backend/backend_transactions.go +++ b/engine/access/rpc/backend/backend_transactions.go @@ -417,7 +417,7 @@ func (b *backendTransactions) getTransactionResultsByBlockIDFromExecutionNode( ) if err != nil { if IsInsufficientExecutionReceipts(err) { - return nil, status.Errorf(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal) } @@ -574,7 +574,7 @@ func (b *backendTransactions) getTransactionResultByIndexFromExecutionNode( ) if err != nil { if IsInsufficientExecutionReceipts(err) { - return nil, status.Errorf(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } return nil, rpc.ConvertError(err, "failed to retrieve result from any execution node", codes.Internal) } @@ -762,7 +762,7 @@ func (b *backendTransactions) getTransactionResultFromExecutionNode( if err != nil { // if no execution receipt were found, return a NotFound GRPC error if IsInsufficientExecutionReceipts(err) { - return nil, status.Errorf(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } return nil, err } @@ -1002,7 +1002,7 @@ func (b *backendTransactions) LookupErrorMessageByTransactionID( ) if err != nil { if IsInsufficientExecutionReceipts(err) { - return "", status.Errorf(codes.NotFound, err.Error()) + return "", status.Error(codes.NotFound, err.Error()) } return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) } @@ -1057,7 +1057,7 @@ func (b *backendTransactions) LookupErrorMessageByIndex( ) if err != nil { if IsInsufficientExecutionReceipts(err) { - return "", status.Errorf(codes.NotFound, err.Error()) + return "", status.Error(codes.NotFound, err.Error()) } return "", rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) } @@ -1117,7 +1117,7 @@ func (b *backendTransactions) LookupErrorMessagesByBlockID( ) if err != nil { if IsInsufficientExecutionReceipts(err) { - return nil, status.Errorf(codes.NotFound, err.Error()) + return nil, status.Error(codes.NotFound, err.Error()) } return nil, rpc.ConvertError(err, "failed to select execution nodes", codes.Internal) } diff --git a/engine/access/rpc/backend/script_executor_test.go b/engine/access/rpc/backend/script_executor_test.go index 70445b8ab8d..2d207194725 100644 --- a/engine/access/rpc/backend/script_executor_test.go +++ b/engine/access/rpc/backend/script_executor_test.go @@ -15,7 +15,6 @@ import ( "github.com/onflow/flow-go/engine/access/index" "github.com/onflow/flow-go/engine/common/version" "github.com/onflow/flow-go/engine/execution/computation/query" - "github.com/onflow/flow-go/engine/execution/computation/query/mock" "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/storage/derived" @@ -109,12 +108,7 @@ func (s *ScriptExecutorSuite) SetupTest() { s.headers = newBlockHeadersStorage(blockchain) s.height = blockchain[0].Header.Height - entropyProvider := testutil.EntropyProviderFixture(nil) - entropyBlock := mock.NewEntropyProviderPerBlock(s.T()) - entropyBlock. - On("AtBlockID", testifyMock.AnythingOfType("flow.Identifier")). - Return(entropyProvider). - Maybe() + protocolState := testutil.ProtocolStateWithSourceFixture(nil) s.snapshot = snapshot.NewSnapshotTree(nil) s.vm = fvm.NewVirtualMachine() @@ -153,7 +147,7 @@ func (s *ScriptExecutorSuite) SetupTest() { s.log, metrics.NewNoopCollector(), s.chain.ChainID(), - entropyBlock, + protocolState, s.headers, indexerCore.RegisterValue, query.NewDefaultConfig(), diff --git a/engine/access/rpc/connection/connection_test.go b/engine/access/rpc/connection/connection_test.go index 33e993f0b8b..160be947508 100644 --- a/engine/access/rpc/connection/connection_test.go +++ b/engine/access/rpc/connection/connection_test.go @@ -34,7 +34,7 @@ func TestProxyAccessAPI(t *testing.T) { metrics := metrics.NewNoopCollector() // create a collection node - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -75,7 +75,7 @@ func TestProxyAccessAPI(t *testing.T) { // make the call to the collection node resp, err := client.Ping(ctx, req) assert.NoError(t, err) - assert.Equal(t, resp, expected) + assert.IsType(t, expected, resp) } func TestProxyExecutionAPI(t *testing.T) { @@ -83,7 +83,7 @@ func TestProxyExecutionAPI(t *testing.T) { metrics := metrics.NewNoopCollector() // create an execution node - en := new(executionNode) + en := newExecutionNode(t) en.start(t) defer en.stop(t) @@ -124,7 +124,7 @@ func TestProxyExecutionAPI(t *testing.T) { // make the call to the execution node resp, err := client.Ping(ctx, req) assert.NoError(t, err) - assert.Equal(t, resp, expected) + assert.IsType(t, expected, resp) } func TestProxyAccessAPIConnectionReuse(t *testing.T) { @@ -132,7 +132,7 @@ func TestProxyAccessAPIConnectionReuse(t *testing.T) { metrics := metrics.NewNoopCollector() // create a collection node - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -186,7 +186,7 @@ func TestProxyAccessAPIConnectionReuse(t *testing.T) { ctx := context.Background() resp, err := accessAPIClient.Ping(ctx, req) assert.NoError(t, err) - assert.Equal(t, resp, expected) + assert.IsType(t, expected, resp) } func TestProxyExecutionAPIConnectionReuse(t *testing.T) { @@ -194,7 +194,7 @@ func TestProxyExecutionAPIConnectionReuse(t *testing.T) { metrics := metrics.NewNoopCollector() // create an execution node - en := new(executionNode) + en := newExecutionNode(t) en.start(t) defer en.stop(t) @@ -248,7 +248,7 @@ func TestProxyExecutionAPIConnectionReuse(t *testing.T) { ctx := context.Background() resp, err := executionAPIClient.Ping(ctx, req) assert.NoError(t, err) - assert.Equal(t, resp, expected) + assert.IsType(t, expected, resp) } // TestExecutionNodeClientTimeout tests that the execution API client times out after the timeout duration @@ -259,7 +259,7 @@ func TestExecutionNodeClientTimeout(t *testing.T) { timeout := 10 * time.Millisecond // create an execution node - en := new(executionNode) + en := newExecutionNode(t) en.start(t) defer en.stop(t) @@ -316,7 +316,7 @@ func TestCollectionNodeClientTimeout(t *testing.T) { timeout := 10 * time.Millisecond // create a collection node - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -371,7 +371,7 @@ func TestConnectionPoolFull(t *testing.T) { metrics := metrics.NewNoopCollector() // create a collection node - cn1, cn2, cn3 := new(collectionNode), new(collectionNode), new(collectionNode) + cn1, cn2, cn3 := newCollectionNode(t), newCollectionNode(t), newCollectionNode(t) cn1.start(t) cn2.start(t) cn3.start(t) @@ -379,23 +379,6 @@ func TestConnectionPoolFull(t *testing.T) { defer cn2.stop(t) defer cn3.stop(t) - expected := &access.PingResponse{} - cn1.handler. - On("Ping", - testifymock.Anything, - testifymock.AnythingOfType("*access.PingRequest")). - Return(expected, nil) - cn2.handler. - On("Ping", - testifymock.Anything, - testifymock.AnythingOfType("*access.PingRequest")). - Return(expected, nil) - cn3.handler. - On("Ping", - testifymock.Anything, - testifymock.AnythingOfType("*access.PingRequest")). - Return(expected, nil) - // create the factory connectionFactory := new(ConnectionFactoryImpl) // set the collection grpc port @@ -467,7 +450,7 @@ func TestConnectionPoolStale(t *testing.T) { metrics := metrics.NewNoopCollector() // create a collection node - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -534,7 +517,7 @@ func TestConnectionPoolStale(t *testing.T) { ctx = context.Background() resp, err := accessAPIClient.Ping(ctx, req) assert.NoError(t, err) - assert.Equal(t, resp, expected) + assert.IsType(t, expected, resp) } // TestExecutionNodeClientClosedGracefully tests the scenario where the execution node client is closed gracefully. @@ -550,7 +533,7 @@ func TestExecutionNodeClientClosedGracefully(t *testing.T) { // Add createExecNode function to recreate it each time for rapid test createExecNode := func() (*executionNode, func()) { - en := new(executionNode) + en := newExecutionNode(t) en.start(t) return en, func() { en.stop(t) @@ -650,7 +633,7 @@ func TestEvictingCacheClients(t *testing.T) { metrics := metrics.NewNoopCollector() // Create a new collection node for testing - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -674,10 +657,6 @@ func TestEvictingCacheClients(t *testing.T) { func(context.Context, *access.PingRequest) error { return nil }, ) - netReq := &access.GetNetworkParametersRequest{} - netResp := &access.GetNetworkParametersResponse{} - cn.handler.On("GetNetworkParameters", testifymock.Anything, netReq).Return(netResp, nil) - // Create the connection factory connectionFactory := new(ConnectionFactoryImpl) // Set the gRPC port @@ -740,7 +719,7 @@ func TestEvictingCacheClients(t *testing.T) { }, 100*time.Millisecond, 10*time.Millisecond, "client timed out closing connection") // Call a gRPC method on the client, requests should be blocked since the connection is invalidated - resp, err := client.GetNetworkParameters(ctx, netReq) + resp, err := client.GetNetworkParameters(ctx, &access.GetNetworkParametersRequest{}) assert.Equal(t, status.Errorf(codes.Unavailable, "the connection to %s was closed", clientAddress), err) assert.Nil(t, resp) @@ -749,9 +728,7 @@ func TestEvictingCacheClients(t *testing.T) { // Call a gRPC method on the client _, err = client.Ping(ctx, pingReq) - // Check that Ping was called - cn.handler.AssertCalled(t, "Ping", testifymock.Anything, pingReq) - assert.NoError(t, err) + require.NoError(t, err) // Wait for the client connection to change state from "Ready" to "Shutdown" as connection was closed. require.Eventually(t, func() bool { @@ -770,7 +747,7 @@ func TestConcurrentConnections(t *testing.T) { // Add createExecNode function to recreate it each time for rapid test createExecNode := func() (*executionNode, func()) { - en := new(executionNode) + en := newExecutionNode(t) en.start(t) return en, func() { en.stop(t) @@ -886,7 +863,7 @@ func TestCircuitBreakerExecutionNode(t *testing.T) { circuitBreakerRestoreTimeout := 1500 * time.Millisecond // Create an execution node for testing. - en := new(executionNode) + en := newExecutionNode(t) en.start(t) defer en.stop(t) @@ -934,8 +911,6 @@ func TestCircuitBreakerExecutionNode(t *testing.T) { // Make the call to the execution node. _, err = client.Ping(ctx, req) - en.handler.AssertCalled(t, "Ping", testifymock.Anything, req) - return time.Since(start), err } @@ -1005,7 +980,7 @@ func TestCircuitBreakerCollectionNode(t *testing.T) { circuitBreakerRestoreTimeout := 1500 * time.Millisecond // Create a collection node for testing. - cn := new(collectionNode) + cn := newCollectionNode(t) cn.start(t) defer cn.stop(t) @@ -1053,8 +1028,6 @@ func TestCircuitBreakerCollectionNode(t *testing.T) { // Make the call to the collection node. _, err = client.Ping(ctx, req) - cn.handler.AssertCalled(t, "Ping", testifymock.Anything, req) - return time.Since(start), err } diff --git a/engine/access/rpc/connection/grpc_compression_benchmark_test.go b/engine/access/rpc/connection/grpc_compression_benchmark_test.go index 6ab86fa39a4..485d0f9c4b2 100644 --- a/engine/access/rpc/connection/grpc_compression_benchmark_test.go +++ b/engine/access/rpc/connection/grpc_compression_benchmark_test.go @@ -38,7 +38,7 @@ func BenchmarkWithDeflateCompression(b *testing.B) { // runBenchmark is a helper function that performs the benchmarking for different compressors. func runBenchmark(b *testing.B, compressorName string) { // create an execution node - en := new(executionNode) + en := newExecutionNode(b) en.start(b) defer en.stop(b) diff --git a/engine/access/rpc/connection/node_mock.go b/engine/access/rpc/connection/node_mock.go index 31a59fd490f..af613f4ffc8 100644 --- a/engine/access/rpc/connection/node_mock.go +++ b/engine/access/rpc/connection/node_mock.go @@ -64,11 +64,19 @@ type executionNode struct { handler *mock.ExecutionAPIServer } +func newExecutionNode(tb testing.TB) *executionNode { + return &executionNode{ + handler: mock.NewExecutionAPIServer(tb), + } +} + func (en *executionNode) start(tb testing.TB) { + if en.handler == nil { + tb.Fatalf("executionNode must be initialized using newExecutionNode") + } + en.setupNode(tb) - handler := new(mock.ExecutionAPIServer) - execution.RegisterExecutionAPIServer(en.server, handler) - en.handler = handler + execution.RegisterExecutionAPIServer(en.server, en.handler) en.node.start(tb) } @@ -81,14 +89,22 @@ type collectionNode struct { handler *mock.AccessAPIServer } -func (cn *collectionNode) start(t *testing.T) { - cn.setupNode(t) - handler := new(mock.AccessAPIServer) - access.RegisterAccessAPIServer(cn.server, handler) - cn.handler = handler - cn.node.start(t) +func newCollectionNode(tb testing.TB) *collectionNode { + return &collectionNode{ + handler: mock.NewAccessAPIServer(tb), + } +} + +func (cn *collectionNode) start(tb testing.TB) { + if cn.handler == nil { + tb.Fatalf("collectionNode must be initialized using newCollectionNode") + } + + cn.setupNode(tb) + access.RegisterAccessAPIServer(cn.server, cn.handler) + cn.node.start(tb) } -func (cn *collectionNode) stop(t *testing.T) { - cn.node.stop(t) +func (cn *collectionNode) stop(tb testing.TB) { + cn.node.stop(tb) } diff --git a/engine/collection/compliance/core_test.go b/engine/collection/compliance/core_test.go index 6fd27bc3963..e2ce8004f56 100644 --- a/engine/collection/compliance/core_test.go +++ b/engine/collection/compliance/core_test.go @@ -376,7 +376,7 @@ func (cs *CoreSuite) TestOnBlockProposal_FailsProtocolStateValidation() { // make sure we fail to extend the state *cs.state = clusterstate.MutableState{} cs.state.On("Final").Return(func() clusterint.Snapshot { return cs.snapshot }) - sentinelErr := state.NewInvalidExtensionError("") + sentinelErr := state.NewInvalidExtensionErrorf("") cs.state.On("Extend", mock.Anything).Return(sentinelErr) cs.proposalViolationNotifier.On("OnInvalidBlockDetected", mock.Anything).Run(func(args mock.Arguments) { err := args.Get(0).(flow.Slashable[model.InvalidProposalError]) @@ -406,7 +406,7 @@ func (cs *CoreSuite) TestOnBlockProposal_FailsProtocolStateValidation() { // make sure we fail to extend the state *cs.state = clusterstate.MutableState{} cs.state.On("Final").Return(func() clusterint.Snapshot { return cs.snapshot }) - cs.state.On("Extend", mock.Anything).Return(state.NewOutdatedExtensionError("")) + cs.state.On("Extend", mock.Anything).Return(state.NewOutdatedExtensionErrorf("")) // the expected error should be handled within the Core err := cs.core.OnBlockProposal(flow.Slashable[*messages.ClusterBlockProposal]{ diff --git a/engine/common/grpc/forwarder/forwarder.go b/engine/common/grpc/forwarder/forwarder.go index a0af264b55a..b685fefe780 100644 --- a/engine/common/grpc/forwarder/forwarder.go +++ b/engine/common/grpc/forwarder/forwarder.go @@ -75,7 +75,7 @@ func (f *Forwarder) reconnectingClient(i int) error { // FaultTolerantClient implements an upstream connection that reconnects on errors // a reasonable amount of time. func (f *Forwarder) FaultTolerantClient() (access.AccessAPIClient, io.Closer, error) { - if f.upstream == nil || len(f.upstream) == 0 { + if len(f.upstream) == 0 { return nil, nil, status.Errorf(codes.Unimplemented, "method not implemented") } @@ -101,5 +101,5 @@ func (f *Forwarder) FaultTolerantClient() (access.AccessAPIClient, io.Closer, er return f.upstream[f.roundRobin].client, f.upstream[f.roundRobin].closer, nil } - return nil, nil, status.Errorf(codes.Unavailable, err.Error()) + return nil, nil, status.Error(codes.Unavailable, err.Error()) } diff --git a/engine/consensus/approvals/testutil.go b/engine/consensus/approvals/testutil.go index 3e511bf76d1..d590bbaf57f 100644 --- a/engine/consensus/approvals/testutil.go +++ b/engine/consensus/approvals/testutil.go @@ -44,7 +44,7 @@ func (s *BaseApprovalsTestSuite) SetupTest() { verifiers := make(flow.IdentifierList, 0) s.AuthorizedVerifiers = make(map[flow.Identifier]*flow.Identity) assignmentBuilder := chunks.NewAssignmentBuilder() - s.Chunks = unittest.ChunkListFixture(50, s.Block.ID()) + s.Chunks = unittest.ChunkListFixture(50, s.Block.ID(), unittest.StateCommitmentFixture()) // mock public key to mock signature verifications s.PublicKey = &module.PublicKey{} diff --git a/engine/consensus/compliance/core_test.go b/engine/consensus/compliance/core_test.go index 41a57a5ffc7..369fd8c29a4 100644 --- a/engine/consensus/compliance/core_test.go +++ b/engine/consensus/compliance/core_test.go @@ -459,7 +459,7 @@ func (cs *CoreSuite) TestOnBlockProposal_FailsProtocolStateValidation() { // make sure we fail to extend the state *cs.state = protocol.ParticipantState{} cs.state.On("Final").Return(func() protint.Snapshot { return cs.snapshot }) - sentinelErr := state.NewInvalidExtensionError("") + sentinelErr := state.NewInvalidExtensionErrorf("") cs.state.On("Extend", mock.Anything, mock.Anything).Return(sentinelErr) cs.proposalViolationNotifier.On("OnInvalidBlockDetected", mock.Anything).Run(func(args mock.Arguments) { err := args.Get(0).(flow.Slashable[model.InvalidProposalError]) @@ -489,7 +489,7 @@ func (cs *CoreSuite) TestOnBlockProposal_FailsProtocolStateValidation() { // make sure we fail to extend the state *cs.state = protocol.ParticipantState{} cs.state.On("Final").Return(func() protint.Snapshot { return cs.snapshot }) - cs.state.On("Extend", mock.Anything, mock.Anything).Return(state.NewOutdatedExtensionError("")) + cs.state.On("Extend", mock.Anything, mock.Anything).Return(state.NewOutdatedExtensionErrorf("")) // the expected error should be handled within the Core err := cs.core.OnBlockProposal(flow.Slashable[*messages.BlockProposal]{ diff --git a/engine/consensus/matching/core_test.go b/engine/consensus/matching/core_test.go index 18af22bde40..762b32e3195 100644 --- a/engine/consensus/matching/core_test.go +++ b/engine/consensus/matching/core_test.go @@ -166,7 +166,7 @@ func (ms *MatchingSuite) TestOnReceiptInvalid() { ) // check that _expected_ failure case of invalid receipt is handled without error - ms.receiptValidator.On("Validate", receipt).Return(engine.NewInvalidInputError("")).Once() + ms.receiptValidator.On("Validate", receipt).Return(engine.NewInvalidInputErrorf("")).Once() wasAdded, err := ms.core.processReceipt(receipt) ms.Require().NoError(err, "invalid receipt should be dropped but not error") ms.Require().False(wasAdded, "invalid receipt should not be added") diff --git a/engine/errors.go b/engine/errors.go index 06f24990052..df31acd58a8 100644 --- a/engine/errors.go +++ b/engine/errors.go @@ -22,10 +22,6 @@ type InvalidInputError struct { err error } -func NewInvalidInputError(msg string) error { - return NewInvalidInputErrorf(msg) -} - func NewInvalidInputErrorf(msg string, args ...interface{}) error { return InvalidInputError{ err: fmt.Errorf(msg, args...), @@ -64,12 +60,6 @@ func NewNetworkTransmissionErrorf(msg string, args ...interface{}) error { } } -func NewNetworkTransmissionError(msg string) error { - return NetworkTransmissionError{ - err: fmt.Errorf(msg), - } -} - func (e NetworkTransmissionError) Unwrap() error { return e.err } diff --git a/engine/execution/checker/core.go b/engine/execution/checker/core.go index a6d3d6f6ad6..3643c174ce3 100644 --- a/engine/execution/checker/core.go +++ b/engine/execution/checker/core.go @@ -134,7 +134,7 @@ func (c *Core) findLastSealedBlock() (*flow.Header, *flow.Header, *flow.Seal, er // findLastExecutedBlockHeight finds the last executed block height func (c *Core) findLastExecutedBlockHeight() (uint64, error) { - height, _, err := c.execState.GetHighestExecutedBlockID(context.Background()) + height, _, err := c.execState.GetLastExecutedBlockID(context.Background()) if err != nil { return 0, fmt.Errorf("could not get the last executed block: %w", err) } diff --git a/engine/execution/checker/core_test.go b/engine/execution/checker/core_test.go index cd27c5cabdc..4130c36ec1d 100644 --- a/engine/execution/checker/core_test.go +++ b/engine/execution/checker/core_test.go @@ -118,7 +118,7 @@ func TestCheckPassIfLastSealedIsNotExecutedAndLastExecutedMatch(t *testing.T) { mockUnexecutedBlock(t, es, lastSealed) // mock the last sealed and is also executed - es.On("GetHighestExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) + es.On("GetLastExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) lastSealedResultAtExecutedHeight, _ := mockSealedBlockAtHeight(t, state, lastExecuted.Height, lastSealedExecuted) mockAtBlockID(t, state, lastSealedExecuted) @@ -143,7 +143,7 @@ func TestCheckFailIfLastSealedIsNotExecutedAndLastExecutedMismatch(t *testing.T) mockUnexecutedBlock(t, es, lastSealed) // mock the last sealed and is also executed - es.On("GetHighestExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) + es.On("GetLastExecutedBlockID", mock.Anything).Return(lastExecuted.Height, lastExecuted.ID(), nil) mockSealedBlockAtHeight(t, state, lastExecuted.Height, lastSealedExecuted) mockAtBlockID(t, state, lastSealedExecuted) diff --git a/engine/execution/computation/computer/computer.go b/engine/execution/computation/computer/computer.go index e863a4d23d1..05e58a4d6f2 100644 --- a/engine/execution/computation/computer/computer.go +++ b/engine/execution/computation/computer/computer.go @@ -115,7 +115,7 @@ type blockComputer struct { spockHasher hash.Hasher receiptHasher hash.Hasher colResCons []result.ExecutedCollectionConsumer - protocolState protocol.State + protocolState protocol.SnapshotExecutionSubsetProvider maxConcurrency int } @@ -146,7 +146,7 @@ func NewBlockComputer( signer module.Local, executionDataProvider provider.Provider, colResCons []result.ExecutedCollectionConsumer, - state protocol.State, + state protocol.SnapshotExecutionSubsetProvider, maxConcurrency int, ) (BlockComputer, error) { if maxConcurrency < 1 { @@ -220,13 +220,7 @@ func (e *blockComputer) queueTransactionRequests( collectionCtx := fvm.NewContextFromParent( e.vmCtx, fvm.WithBlockHeader(blockHeader), - // `protocol.Snapshot` implements `EntropyProvider` interface - // Note that `Snapshot` possible errors for RandomSource() are: - // - storage.ErrNotFound if the QC is unknown. - // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown - // However, at this stage, snapshot reference block should be known and the QC should also be known, - // so no error is expected in normal operations, as required by `EntropyProvider`. - fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)), + fvm.WithProtocolStateSnapshot(e.protocolState.AtBlockID(blockId)), ) for idx, collection := range rawCollections { @@ -261,13 +255,7 @@ func (e *blockComputer) queueTransactionRequests( systemCtx := fvm.NewContextFromParent( e.systemChunkCtx, fvm.WithBlockHeader(blockHeader), - // `protocol.Snapshot` implements `EntropyProvider` interface - // Note that `Snapshot` possible errors for RandomSource() are: - // - storage.ErrNotFound if the QC is unknown. - // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown - // However, at this stage, snapshot reference block should be known and the QC should also be known, - // so no error is expected in normal operations, as required by `EntropyProvider`. - fvm.WithEntropyProvider(e.protocolState.AtBlockID(blockId)), + fvm.WithProtocolStateSnapshot(e.protocolState.AtBlockID(blockId)), ) systemCollectionLogger := systemCtx.Logger.With(). Str("block_id", blockIdStr). diff --git a/engine/execution/computation/computer/computer_test.go b/engine/execution/computation/computer/computer_test.go index 0d7899100ad..27b81d39c44 100644 --- a/engine/execution/computation/computer/computer_test.go +++ b/engine/execution/computation/computer/computer_test.go @@ -705,7 +705,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { }, ), ), - fvm.WithReadVersionFromNodeVersionBeacon(false), ) vm := fvm.NewVirtualMachine() @@ -818,7 +817,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { func(_ runtime.Config) runtime.Runtime { return rt })), - fvm.WithReadVersionFromNodeVersionBeacon(false), ) vm := fvm.NewVirtualMachine() @@ -933,7 +931,6 @@ func TestBlockExecutor_ExecuteBlock(t *testing.T) { func(_ runtime.Config) runtime.Runtime { return rt })), - fvm.WithReadVersionFromNodeVersionBeacon(false), ) vm := fvm.NewVirtualMachine() diff --git a/engine/execution/computation/manager.go b/engine/execution/computation/manager.go index e13a2d03791..ee754c18392 100644 --- a/engine/execution/computation/manager.go +++ b/engine/execution/computation/manager.go @@ -91,7 +91,7 @@ func New( metrics module.ExecutionMetrics, tracer module.Tracer, me module.Local, - protoState protocol.State, + protoState protocol.SnapshotExecutionSubsetProvider, vmCtx fvm.Context, committer computer.ViewCommitter, executionDataProvider provider.Provider, @@ -140,7 +140,7 @@ func New( vm, vmCtx, derivedChainData, - query.NewProtocolStateWrapper(protoState), + protoState, ) e := Manager{ diff --git a/engine/execution/computation/manager_benchmark_test.go b/engine/execution/computation/manager_benchmark_test.go index d22b1ec9a8b..709e9740212 100644 --- a/engine/execution/computation/manager_benchmark_test.go +++ b/engine/execution/computation/manager_benchmark_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - blockstore "github.com/ipfs/boxo/blockstore" + "github.com/ipfs/boxo/blockstore" "github.com/ipfs/go-datastore" dssync "github.com/ipfs/go-datastore/sync" "github.com/onflow/cadence/runtime" diff --git a/engine/execution/computation/query/entropy_provider.go b/engine/execution/computation/query/entropy_provider.go deleted file mode 100644 index b4b26855a89..00000000000 --- a/engine/execution/computation/query/entropy_provider.go +++ /dev/null @@ -1,40 +0,0 @@ -package query - -import ( - "github.com/onflow/flow-go/fvm/environment" - "github.com/onflow/flow-go/model/flow" - "github.com/onflow/flow-go/state/protocol" -) - -// EntropyProviderPerBlock is an abstraction for entropy providers -// that can be used in `QueryExecutor`. -// -// `EntropyProvider` is defined in `fvm/environment` and abstracts the -// distributed random source used by the protocol. -// -// For a full-protocol node implementation , `EntropyProvider` is implemented -// by the protocol `Snapshot`, while `EntropyProviderPerBlock` is implemented -// by the protocol `State`. -// For nodes answering script queries that do not participate in the protocol, -// `EntropyProvider` and `EntropyProviderPerBlock` can be implemented by other -// components that provide the source of randomness for each block. -type EntropyProviderPerBlock interface { - // AtBlockID returns an entropy provider at the given block ID. - AtBlockID(blockID flow.Identifier) environment.EntropyProvider -} - -// protocolStateWrapper implements `EntropyProviderPerBlock` -var _ EntropyProviderPerBlock = protocolStateWrapper{} - -type protocolStateWrapper struct { - protocol.State -} - -// NewProtocolStateWrapper wraps a protocol.State input as an `EntropyProviderPerBlock` -func NewProtocolStateWrapper(s protocol.State) EntropyProviderPerBlock { - return protocolStateWrapper{s} -} - -func (p protocolStateWrapper) AtBlockID(blockID flow.Identifier) environment.EntropyProvider { - return environment.EntropyProvider(p.State.AtBlockID(blockID)) -} diff --git a/engine/execution/computation/query/executor.go b/engine/execution/computation/query/executor.go index 224d1fc0e65..1027cebcbfa 100644 --- a/engine/execution/computation/query/executor.go +++ b/engine/execution/computation/query/executor.go @@ -18,6 +18,7 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/utils/debug" "github.com/onflow/flow-go/utils/rand" ) @@ -110,14 +111,14 @@ func NewDefaultConfig() QueryConfig { } type QueryExecutor struct { - config QueryConfig - logger zerolog.Logger - metrics module.ExecutionMetrics - vm fvm.VM - vmCtx fvm.Context - derivedChainData *derived.DerivedChainData - rngLock *sync.Mutex - entropyPerBlock EntropyProviderPerBlock + config QueryConfig + logger zerolog.Logger + metrics module.ExecutionMetrics + vm fvm.VM + vmCtx fvm.Context + derivedChainData *derived.DerivedChainData + rngLock *sync.Mutex + protocolStateSnapshot protocol.SnapshotExecutionSubsetProvider } var _ Executor = &QueryExecutor{} @@ -129,20 +130,20 @@ func NewQueryExecutor( vm fvm.VM, vmCtx fvm.Context, derivedChainData *derived.DerivedChainData, - entropyPerBlock EntropyProviderPerBlock, + protocolStateSnapshot protocol.SnapshotExecutionSubsetProvider, ) *QueryExecutor { if config.ComputationLimit > 0 { vmCtx = fvm.NewContextFromParent(vmCtx, fvm.WithComputationLimit(config.ComputationLimit)) } return &QueryExecutor{ - config: config, - logger: logger, - metrics: metrics, - vm: vm, - vmCtx: vmCtx, - derivedChainData: derivedChainData, - rngLock: &sync.Mutex{}, - entropyPerBlock: entropyPerBlock, + config: config, + logger: logger, + metrics: metrics, + vm: vm, + vmCtx: vmCtx, + derivedChainData: derivedChainData, + rngLock: &sync.Mutex{}, + protocolStateSnapshot: protocolStateSnapshot, } } @@ -215,7 +216,7 @@ func (e *QueryExecutor) ExecuteScript( fvm.NewContextFromParent( e.vmCtx, fvm.WithBlockHeader(blockHeader), - fvm.WithEntropyProvider(e.entropyPerBlock.AtBlockID(blockHeader.ID())), + fvm.WithProtocolStateSnapshot(e.protocolStateSnapshot.AtBlockID(blockHeader.ID())), fvm.WithDerivedBlockData( e.derivedChainData.NewDerivedBlockDataForScript(blockHeader.ID()))), fvm.NewScriptWithContextAndArgs(script, requestCtx, arguments...), diff --git a/engine/execution/computation/query/mock/entropy_provider_per_block.go b/engine/execution/computation/query/mock/entropy_provider_per_block.go deleted file mode 100644 index 753f3f1ff3a..00000000000 --- a/engine/execution/computation/query/mock/entropy_provider_per_block.go +++ /dev/null @@ -1,49 +0,0 @@ -// Code generated by mockery v2.43.2. DO NOT EDIT. - -package mock - -import ( - environment "github.com/onflow/flow-go/fvm/environment" - flow "github.com/onflow/flow-go/model/flow" - - mock "github.com/stretchr/testify/mock" -) - -// EntropyProviderPerBlock is an autogenerated mock type for the EntropyProviderPerBlock type -type EntropyProviderPerBlock struct { - mock.Mock -} - -// AtBlockID provides a mock function with given fields: blockID -func (_m *EntropyProviderPerBlock) AtBlockID(blockID flow.Identifier) environment.EntropyProvider { - ret := _m.Called(blockID) - - if len(ret) == 0 { - panic("no return value specified for AtBlockID") - } - - var r0 environment.EntropyProvider - if rf, ok := ret.Get(0).(func(flow.Identifier) environment.EntropyProvider); ok { - r0 = rf(blockID) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(environment.EntropyProvider) - } - } - - return r0 -} - -// NewEntropyProviderPerBlock creates a new instance of EntropyProviderPerBlock. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewEntropyProviderPerBlock(t interface { - mock.TestingT - Cleanup(func()) -}) *EntropyProviderPerBlock { - mock := &EntropyProviderPerBlock{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/engine/execution/computation/snapshot_provider.go b/engine/execution/computation/snapshot_provider.go new file mode 100644 index 00000000000..4819ca4b16d --- /dev/null +++ b/engine/execution/computation/snapshot_provider.go @@ -0,0 +1,29 @@ +package computation + +import ( + "github.com/onflow/flow-go/model/flow" + "github.com/onflow/flow-go/state/protocol" +) + +// SnapshotExecutionSubset is a subset of the protocol state snapshot that is needed by the FVM +var _ protocol.SnapshotExecutionSubset = (protocol.Snapshot)(nil) + +// protocolStateWrapper just wraps the protocol.State and returns a SnapshotExecutionSubset +// from the AtBlockID method instead of the protocol.Snapshot interface. +type protocolStateWrapper struct { + protocol.State +} + +// protocolStateWrapper implements `EntropyProviderPerBlock` +var _ protocol.SnapshotExecutionSubsetProvider = (*protocolStateWrapper)(nil) + +func (p protocolStateWrapper) AtBlockID(blockID flow.Identifier) protocol.SnapshotExecutionSubset { + return p.State.AtBlockID(blockID) +} + +// NewProtocolStateWrapper wraps the protocol.State input so that the AtBlockID method returns a +// SnapshotExecutionSubset instead of the protocol.Snapshot interface. +// This is used in the FVM for execution. +func NewProtocolStateWrapper(s protocol.State) protocol.SnapshotExecutionSubsetProvider { + return protocolStateWrapper{s} +} diff --git a/engine/execution/ingestion/loader/unexecuted_loader.go b/engine/execution/ingestion/loader/unexecuted_loader.go index 6e2dd273452..caf90ec5347 100644 --- a/engine/execution/ingestion/loader/unexecuted_loader.go +++ b/engine/execution/ingestion/loader/unexecuted_loader.go @@ -47,7 +47,7 @@ func (e *UnexecutedLoader) LoadUnexecuted(ctx context.Context) ([]flow.Identifie // a root block will fail, because the root block doesn't have a parent block, and could not // get the result of it. // TODO: remove this, when saving a executed block is transactional - lastExecutedHeight, lastExecutedID, err := e.execState.GetHighestExecutedBlockID(ctx) + lastExecutedHeight, lastExecutedID, err := e.execState.GetLastExecutedBlockID(ctx) if err != nil { return nil, fmt.Errorf("could not get last executed: %w", err) } diff --git a/engine/execution/ingestion/loader/unexecuted_loader_test.go b/engine/execution/ingestion/loader/unexecuted_loader_test.go index df5ef452606..cb337e53aa0 100644 --- a/engine/execution/ingestion/loader/unexecuted_loader_test.go +++ b/engine/execution/ingestion/loader/unexecuted_loader_test.go @@ -35,7 +35,7 @@ func newMockExecutionState(seal *flow.Seal, genesis *flow.Header) *mockExecution es := &mockExecutionState{ commits: commits, } - es.On("GetHighestExecutedBlockID", mock.Anything).Return(genesis.Height, genesis.ID(), nil) + es.On("GetLastExecutedBlockID", mock.Anything).Return(genesis.Height, genesis.ID(), nil) return es } diff --git a/engine/execution/state/mock/execution_state.go b/engine/execution/state/mock/execution_state.go index e10988cdd4f..59ef8e56d5d 100644 --- a/engine/execution/state/mock/execution_state.go +++ b/engine/execution/state/mock/execution_state.go @@ -117,12 +117,40 @@ func (_m *ExecutionState) GetExecutionResultID(_a0 context.Context, _a1 flow.Ide return r0, r1 } -// GetHighestExecutedBlockID provides a mock function with given fields: _a0 -func (_m *ExecutionState) GetHighestExecutedBlockID(_a0 context.Context) (uint64, flow.Identifier, error) { +// GetHighestFinalizedExecuted provides a mock function with given fields: +func (_m *ExecutionState) GetHighestFinalizedExecuted() (uint64, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GetHighestFinalizedExecuted") + } + + var r0 uint64 + var r1 error + if rf, ok := ret.Get(0).(func() (uint64, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetLastExecutedBlockID provides a mock function with given fields: _a0 +func (_m *ExecutionState) GetLastExecutedBlockID(_a0 context.Context) (uint64, flow.Identifier, error) { ret := _m.Called(_a0) if len(ret) == 0 { - panic("no return value specified for GetHighestExecutedBlockID") + panic("no return value specified for GetLastExecutedBlockID") } var r0 uint64 @@ -154,34 +182,6 @@ func (_m *ExecutionState) GetHighestExecutedBlockID(_a0 context.Context) (uint64 return r0, r1, r2 } -// GetHighestFinalizedExecuted provides a mock function with given fields: -func (_m *ExecutionState) GetHighestFinalizedExecuted() (uint64, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetHighestFinalizedExecuted") - } - - var r0 uint64 - var r1 error - if rf, ok := ret.Get(0).(func() (uint64, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() uint64); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(uint64) - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // IsBlockExecuted provides a mock function with given fields: height, blockID func (_m *ExecutionState) IsBlockExecuted(height uint64, blockID flow.Identifier) (bool, error) { ret := _m.Called(height, blockID) @@ -278,12 +278,12 @@ func (_m *ExecutionState) StateCommitmentByBlockID(_a0 flow.Identifier) (flow.St return r0, r1 } -// UpdateHighestExecutedBlockIfHigher provides a mock function with given fields: _a0, _a1 -func (_m *ExecutionState) UpdateHighestExecutedBlockIfHigher(_a0 context.Context, _a1 *flow.Header) error { +// UpdateLastExecutedBlock provides a mock function with given fields: _a0, _a1 +func (_m *ExecutionState) UpdateLastExecutedBlock(_a0 context.Context, _a1 *flow.Header) error { ret := _m.Called(_a0, _a1) if len(ret) == 0 { - panic("no return value specified for UpdateHighestExecutedBlockIfHigher") + panic("no return value specified for UpdateLastExecutedBlock") } var r0 error diff --git a/engine/execution/state/mock/read_only_execution_state.go b/engine/execution/state/mock/read_only_execution_state.go index 68d4a9bcc3f..0df72b73c33 100644 --- a/engine/execution/state/mock/read_only_execution_state.go +++ b/engine/execution/state/mock/read_only_execution_state.go @@ -115,12 +115,12 @@ func (_m *ReadOnlyExecutionState) GetExecutionResultID(_a0 context.Context, _a1 return r0, r1 } -// GetHighestExecutedBlockID provides a mock function with given fields: _a0 -func (_m *ReadOnlyExecutionState) GetHighestExecutedBlockID(_a0 context.Context) (uint64, flow.Identifier, error) { +// GetLastExecutedBlockID provides a mock function with given fields: _a0 +func (_m *ReadOnlyExecutionState) GetLastExecutedBlockID(_a0 context.Context) (uint64, flow.Identifier, error) { ret := _m.Called(_a0) if len(ret) == 0 { - panic("no return value specified for GetHighestExecutedBlockID") + panic("no return value specified for GetLastExecutedBlockID") } var r0 uint64 diff --git a/engine/execution/state/state.go b/engine/execution/state/state.go index af73c3d49a8..f95f15a3efd 100644 --- a/engine/execution/state/state.go +++ b/engine/execution/state/state.go @@ -35,7 +35,7 @@ type ReadOnlyExecutionState interface { GetExecutionResultID(context.Context, flow.Identifier) (flow.Identifier, error) - GetHighestExecutedBlockID(context.Context) (uint64, flow.Identifier, error) + GetLastExecutedBlockID(context.Context) (uint64, flow.Identifier, error) } // ScriptExecutionState is a subset of the `state.ExecutionState` interface purposed to only access the state @@ -79,7 +79,7 @@ type FinalizedExecutionState interface { type ExecutionState interface { ReadOnlyExecutionState - UpdateHighestExecutedBlockIfHigher(context.Context, *flow.Header) error + UpdateLastExecutedBlock(context.Context, *flow.Header) error SaveExecutionResults( ctx context.Context, @@ -385,7 +385,7 @@ func (s *state) SaveExecutionResults( } //outside batch because it requires read access - err = s.UpdateHighestExecutedBlockIfHigher(childCtx, result.ExecutableBlock.Block.Header) + err = s.UpdateLastExecutedBlock(childCtx, result.ExecutableBlock.Block.Header) if err != nil { return fmt.Errorf("cannot update highest executed block: %w", err) } @@ -473,17 +473,12 @@ func (s *state) saveExecutionResults( return nil } -func (s *state) UpdateHighestExecutedBlockIfHigher(ctx context.Context, header *flow.Header) error { - if s.tracer != nil { - span, _ := s.tracer.StartSpanFromContext(ctx, trace.EXEUpdateHighestExecutedBlockIfHigher) - defer span.End() - } - - return operation.RetryOnConflict(s.db.Update, procedure.UpdateHighestExecutedBlockIfHigher(header)) +func (s *state) UpdateLastExecutedBlock(ctx context.Context, header *flow.Header) error { + return operation.RetryOnConflict(s.db.Update, procedure.UpdateLastExecutedBlock(header)) } // deprecated by storehouse's GetHighestFinalizedExecuted -func (s *state) GetHighestExecutedBlockID(ctx context.Context) (uint64, flow.Identifier, error) { +func (s *state) GetLastExecutedBlockID(ctx context.Context) (uint64, flow.Identifier, error) { if s.enableRegisterStore { // when storehouse is enabled, the highest executed block is consisted as // the highest finalized and executed block @@ -501,7 +496,7 @@ func (s *state) GetHighestExecutedBlockID(ctx context.Context) (uint64, flow.Ide var blockID flow.Identifier var height uint64 - err := s.db.View(procedure.GetHighestExecutedBlock(&height, &blockID)) + err := s.db.View(procedure.GetLastExecutedBlock(&height, &blockID)) if err != nil { return 0, flow.ZeroID, err } @@ -522,7 +517,7 @@ func (s *state) GetHighestFinalizedExecuted() (uint64, error) { } // last executed height - executedHeight, _, err := s.GetHighestExecutedBlockID(context.Background()) + executedHeight, _, err := s.GetLastExecutedBlockID(context.Background()) if err != nil { return 0, fmt.Errorf("could not get highest executed block: %w", err) } diff --git a/engine/execution/testutil/fixtures.go b/engine/execution/testutil/fixtures.go index 38ec08afbe4..ef2506eb77a 100644 --- a/engine/execution/testutil/fixtures.go +++ b/engine/execution/testutil/fixtures.go @@ -11,7 +11,6 @@ import ( "github.com/onflow/cadence/encoding/ccf" jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/stdlib" - "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/onflow/crypto" @@ -30,7 +29,6 @@ import ( "github.com/onflow/flow-go/module/epochs" "github.com/onflow/flow-go/module/executiondatasync/execution_data" "github.com/onflow/flow-go/state/protocol" - protocolMock "github.com/onflow/flow-go/state/protocol/mock" "github.com/onflow/flow-go/utils/unittest" ) @@ -659,13 +657,52 @@ func EntropyProviderFixture(source []byte) environment.EntropyProvider { // supports AtBlockID to return a snapshot mock. // The snapshot mock only supports RandomSource(). // If input is nil, a random source fixture is generated. -func ProtocolStateWithSourceFixture(source []byte) protocol.State { +func ProtocolStateWithSourceFixture(source []byte) protocol.SnapshotExecutionSubsetProvider { if source == nil { source = unittest.SignatureFixture() } - snapshot := &protocolMock.Snapshot{} - snapshot.On("RandomSource").Return(source, nil) - state := protocolMock.State{} - state.On("AtBlockID", mock.Anything).Return(snapshot) - return &state + snapshot := mockSnapshotSubset{ + randomSourceFunc: func() ([]byte, error) { + return source, nil + }, + versionBeaconFunc: func() (*flow.SealedVersionBeacon, error) { + return &flow.SealedVersionBeacon{VersionBeacon: unittest.VersionBeaconFixture()}, nil + }, + } + + provider := mockProtocolStateSnapshotProvider{ + snapshotFunc: func(blockID flow.Identifier) protocol.SnapshotExecutionSubset { + return snapshot + }, + } + return provider +} + +type mockProtocolStateSnapshotProvider struct { + snapshotFunc func(blockID flow.Identifier) protocol.SnapshotExecutionSubset +} + +func (m mockProtocolStateSnapshotProvider) AtBlockID(blockID flow.Identifier) protocol.SnapshotExecutionSubset { + return m.snapshotFunc(blockID) } + +type mockSnapshotSubset struct { + randomSourceFunc func() ([]byte, error) + versionBeaconFunc func() (*flow.SealedVersionBeacon, error) +} + +func (m mockSnapshotSubset) RandomSource() ([]byte, error) { + if m.randomSourceFunc == nil { + return nil, errors.New("random source not implemented") + } + return m.randomSourceFunc() +} + +func (m mockSnapshotSubset) VersionBeacon() (*flow.SealedVersionBeacon, error) { + if m.versionBeaconFunc == nil { + return nil, errors.New("version beacon not implemented") + } + return m.versionBeaconFunc() +} + +var _ protocol.SnapshotExecutionSubset = (*mockSnapshotSubset)(nil) diff --git a/engine/testutil/mock/nodes.go b/engine/testutil/mock/nodes.go index aae7383ada0..ff9654eacef 100644 --- a/engine/testutil/mock/nodes.go +++ b/engine/testutil/mock/nodes.go @@ -254,7 +254,7 @@ func (en ExecutionNode) Done(cancelFunc context.CancelFunc) { } func (en ExecutionNode) AssertHighestExecutedBlock(t *testing.T, header *flow.Header) { - height, blockID, err := en.ExecutionState.GetHighestExecutedBlockID(context.Background()) + height, blockID, err := en.ExecutionState.GetLastExecutedBlockID(context.Background()) require.NoError(t, err) require.Equal(t, header.ID(), blockID) diff --git a/engine/testutil/nodes.go b/engine/testutil/nodes.go index a602e628565..d8ce9e692d2 100644 --- a/engine/testutil/nodes.go +++ b/engine/testutil/nodes.go @@ -684,7 +684,7 @@ func ExecutionNode(t *testing.T, hub *stub.Hub, identity bootstrap.NodeInfo, ide node.Metrics, node.Tracer, node.Me, - node.State, + computation.NewProtocolStateWrapper(node.State), vmCtx, committer, prov, diff --git a/engine/verification/utils/unittest/fixture.go b/engine/verification/utils/unittest/fixture.go index 25cea9d934c..08cab364abd 100644 --- a/engine/verification/utils/unittest/fixture.go +++ b/engine/verification/utils/unittest/fixture.go @@ -290,6 +290,8 @@ func ExecutionResultFixture(t *testing.T, me.On("SignFunc", mock.Anything, mock.Anything, mock.Anything). Return(nil, nil) + protocolState := testutil.ProtocolStateWithSourceFixture(source) + // create BlockComputer bc, err := computer.NewBlockComputer( vm, @@ -301,7 +303,7 @@ func ExecutionResultFixture(t *testing.T, me, prov, nil, - testutil.ProtocolStateWithSourceFixture(source), + protocolState, testMaxConcurrency) require.NoError(t, err) @@ -501,7 +503,7 @@ func ExecutionResultForkFixture(t *testing.T) (*flow.ExecutionResult, *flow.Exec resultB := &flow.ExecutionResult{ PreviousResultID: resultA.PreviousResultID, BlockID: resultA.BlockID, - Chunks: append(flow.ChunkList{resultA.Chunks[0]}, unittest.ChunkListFixture(1, resultA.BlockID)...), + Chunks: append(flow.ChunkList{resultA.Chunks[0]}, unittest.ChunkListFixture(1, resultA.BlockID, resultA.Chunks[0].EndState)...), ServiceEvents: nil, } diff --git a/engine/verification/verifier/verifiers.go b/engine/verification/verifier/verifiers.go index 7ccef14982f..62407852406 100644 --- a/engine/verification/verifier/verifiers.go +++ b/engine/verification/verifier/verifiers.go @@ -18,6 +18,7 @@ import ( "github.com/onflow/flow-go/module" "github.com/onflow/flow-go/module/chunks" "github.com/onflow/flow-go/module/metrics" + "github.com/onflow/flow-go/module/util" "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/storage" storagepebble "github.com/onflow/flow-go/storage/pebble" @@ -125,6 +126,14 @@ func verifyConcurrently( var lowestErrHeight uint64 = ^uint64(0) // Initialize to max value of uint64 var mu sync.Mutex // To protect access to lowestErr and lowestErrHeight + lg := util.LogProgress( + log.Logger, + util.DefaultLogProgressConfig( + fmt.Sprintf("verifying heights progress for [%v:%v]", from, to), + int(to+1-from), + ), + ) + // Worker function worker := func() { for { @@ -154,6 +163,8 @@ func verifyConcurrently( } else { log.Info().Uint64("height", height).Msg("verified height successfully") } + + lg(1) // log progress } } } diff --git a/fvm/context.go b/fvm/context.go index 0abb46e4d88..5582b9ac38e 100644 --- a/fvm/context.go +++ b/fvm/context.go @@ -13,6 +13,7 @@ import ( "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/state/protocol" ) const ( @@ -166,16 +167,6 @@ func WithEventCollectionSizeLimit(limit uint64) Option { } } -// WithEntropyProvider sets the entropy provider of a virtual machine context. -// -// The VM uses the input to provide entropy to the Cadence runtime randomness functions. -func WithEntropyProvider(source environment.EntropyProvider) Option { - return func(ctx Context) Context { - ctx.EntropyProvider = source - return ctx - } -} - // WithBlockHeader sets the block header for a virtual machine context. // // The VM uses the header to provide current block information to the Cadence runtime. @@ -381,11 +372,34 @@ func WithAllowProgramCacheWritesInScriptsEnabled(enabled bool) Option { } } -// WithReadVersionFromNodeVersionBeacon sets whether the version from the node version beacon should be read -// this should only be disabled for testing -func WithReadVersionFromNodeVersionBeacon(enabled bool) Option { +// WithEntropyProvider sets the entropy provider of a virtual machine context. +// +// The VM uses the input to provide entropy to the Cadence runtime randomness functions. +func WithEntropyProvider(source environment.EntropyProvider) Option { + return func(ctx Context) Context { + ctx.EntropyProvider = source + return ctx + } +} + +// WithExecutionVersionProvider sets the execution version provider of a virtual machine context. +// +// this is used to provide the execution version to the Cadence runtime. +func WithExecutionVersionProvider(provider environment.ExecutionVersionProvider) Option { + return func(ctx Context) Context { + ctx.ExecutionVersionProvider = provider + return ctx + } +} + +// WithProtocolStateSnapshot sets all the necessary components from a subset of the protocol state +// to the virtual machine context. +func WithProtocolStateSnapshot(snapshot protocol.SnapshotExecutionSubset) Option { return func(ctx Context) Context { - ctx.ReadVersionFromNodeVersionBeacon = enabled + + ctx = WithEntropyProvider(snapshot)(ctx) + + ctx = WithExecutionVersionProvider(environment.NewVersionBeaconExecutionVersionProvider(snapshot.VersionBeacon))(ctx) return ctx } } diff --git a/fvm/environment/env.go b/fvm/environment/env.go index e23e9c64deb..8f5616a033c 100644 --- a/fvm/environment/env.go +++ b/fvm/environment/env.go @@ -62,12 +62,6 @@ type Environment interface { error, ) - // GetCurrentVersionBoundary executes the getCurrentVersionBoundary function on the NodeVersionBeacon contract. - // the function will return the version boundary (version, block height) that is currently in effect. - // the version boundary currently in effect is the highest one not above the current block height. - // if there is no existing version boundary lower than the current block height, the function will return version 0 and block height 0. - GetCurrentVersionBoundary() (cadence.Value, error) - // AccountInfo GetAccount(address flow.Address) (*flow.Account, error) GetAccountKeys(address flow.Address) ([]flow.AccountPublicKey, error) @@ -108,6 +102,7 @@ type EnvironmentParams struct { ScriptInfoParams EntropyProvider + ExecutionVersionProvider ContractUpdaterParams } @@ -117,12 +112,13 @@ func DefaultEnvironmentParams() EnvironmentParams { Chain: flow.Mainnet.Chain(), ServiceAccountEnabled: true, - RuntimeParams: DefaultRuntimeParams(), - ProgramLoggerParams: DefaultProgramLoggerParams(), - EventEmitterParams: DefaultEventEmitterParams(), - BlockInfoParams: DefaultBlockInfoParams(), - TransactionInfoParams: DefaultTransactionInfoParams(), - ContractUpdaterParams: DefaultContractUpdaterParams(), + RuntimeParams: DefaultRuntimeParams(), + ProgramLoggerParams: DefaultProgramLoggerParams(), + EventEmitterParams: DefaultEventEmitterParams(), + BlockInfoParams: DefaultBlockInfoParams(), + TransactionInfoParams: DefaultTransactionInfoParams(), + ContractUpdaterParams: DefaultContractUpdaterParams(), + ExecutionVersionProvider: ZeroExecutionVersionProvider{}, } } diff --git a/fvm/environment/facade_env.go b/fvm/environment/facade_env.go index 57b01d1a853..b1547f5702f 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -109,7 +109,7 @@ func newFacadeEnvironment( SystemContracts: systemContracts, MinimumCadenceRequiredVersion: NewMinimumCadenceRequiredVersion( - txnState, + params.ExecutionVersionProvider, ), UUIDGenerator: NewUUIDGenerator( diff --git a/fvm/environment/minimum_required_version.go b/fvm/environment/minimum_required_version.go index 3095c33cda9..794761dc0b8 100644 --- a/fvm/environment/minimum_required_version.go +++ b/fvm/environment/minimum_required_version.go @@ -3,9 +3,52 @@ package environment import ( "github.com/coreos/go-semver/semver" - "github.com/onflow/flow-go/fvm/storage/state" + "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/model/flow" ) +type ExecutionVersionProvider interface { + ExecutionVersion() (semver.Version, error) +} + +type GetVersionBeaconFunc func() (*flow.SealedVersionBeacon, error) + +type VersionBeaconExecutionVersionProvider struct { + getVersionBeacon GetVersionBeaconFunc +} + +func NewVersionBeaconExecutionVersionProvider(getVersionBeacon GetVersionBeaconFunc) VersionBeaconExecutionVersionProvider { + return VersionBeaconExecutionVersionProvider{ + getVersionBeacon: getVersionBeacon, + } +} + +func (v VersionBeaconExecutionVersionProvider) ExecutionVersion() (semver.Version, error) { + vb, err := v.getVersionBeacon() + if err != nil { + return semver.Version{}, err + } + // Special case. If there are no version boundaries, then the execution version is 0.0.0. + if vb == nil || len(vb.VersionBoundaries) == 0 { + return semver.Version{}, nil + } + + // by definition zero boundary is the last most recent past boundary + boundary := vb.VersionBoundaries[0] + sv, err := boundary.Semver() + if err != nil { + return semver.Version{}, err + } + + return *sv, nil +} + +type ZeroExecutionVersionProvider struct{} + +func (v ZeroExecutionVersionProvider) ExecutionVersion() (semver.Version, error) { + return semver.Version{}, nil +} + // MinimumCadenceRequiredVersion returns the minimum required cadence version for the current environment // in semver format. type MinimumCadenceRequiredVersion interface { @@ -13,14 +56,14 @@ type MinimumCadenceRequiredVersion interface { } type minimumCadenceRequiredVersion struct { - txnPreparer state.NestedTransactionPreparer + executionVersionProvider ExecutionVersionProvider } func NewMinimumCadenceRequiredVersion( - txnPreparer state.NestedTransactionPreparer, + executionVersionProvider ExecutionVersionProvider, ) MinimumCadenceRequiredVersion { return minimumCadenceRequiredVersion{ - txnPreparer: txnPreparer, + executionVersionProvider: executionVersionProvider, } } @@ -43,7 +86,7 @@ func NewMinimumCadenceRequiredVersion( // and map it to the cadence version to be used by cadence to decide feature flag status. // // For instance, let’s say all ENs are running flow-go v0.37.0 with cadence v1. -// We first create a version mapping entry for flow-go v0.37.1 to cadence v2, and roll out v0.37.1 to all ENs. +// We first create a version mapping entry for flow-go v0.37.1 to cadence v2, and roll out v0.37.1 to all ENs.Z // v0.37.1 ENs will produce the same result as v0.37.0 ENs, because the current version beacon still returns v0.37.0, // which maps zero cadence version, and cadence will keep the feature flag off. // @@ -57,10 +100,13 @@ func NewMinimumCadenceRequiredVersion( // After height 1000 have been sealed, we can roll out v0.37.2 to all ENs with cadence v3, and it will produce the consistent // result as v0.37.1. func (c minimumCadenceRequiredVersion) MinimumRequiredVersion() (string, error) { - executionParameters := c.txnPreparer.ExecutionParameters() + executionVersion, err := c.executionVersionProvider.ExecutionVersion() + if err != nil { + return "", errors.NewExecutionVersionProviderFailure(err) + } // map the minimum required flow-go version to a minimum required cadence version - cadenceVersion := mapToCadenceVersion(executionParameters.ExecutionVersion, minimumFvmToMinimumCadenceVersionMapping) + cadenceVersion := mapToCadenceVersion(executionVersion, minimumFvmToMinimumCadenceVersionMapping) return cadenceVersion.String(), nil } @@ -92,7 +138,7 @@ var minimumFvmToMinimumCadenceVersionMapping = FlowGoToCadenceVersionMapping{ // } -func SetFVMToCadenceVersionMappingForTestingOnly(mapping FlowGoToCadenceVersionMapping) { +func setFVMToCadenceVersionMappingForTestingOnly(mapping FlowGoToCadenceVersionMapping) { minimumFvmToMinimumCadenceVersionMapping = mapping } diff --git a/fvm/environment/minimum_required_version_test.go b/fvm/environment/minimum_required_version_test.go index a72e10567df..ca33305c441 100644 --- a/fvm/environment/minimum_required_version_test.go +++ b/fvm/environment/minimum_required_version_test.go @@ -5,6 +5,8 @@ import ( "github.com/coreos/go-semver/semver" "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/model/flow" ) func Test_MapToCadenceVersion(t *testing.T) { @@ -61,3 +63,101 @@ func Test_MapToCadenceVersion(t *testing.T) { require.Equal(t, cadenceV1, version) }) } + +func Test_VersionBeaconAsDataSource(t *testing.T) { + t.Run("no version beacon", func(t *testing.T) { + versionBeacon := VersionBeaconExecutionVersionProvider{ + getVersionBeacon: func() (*flow.SealedVersionBeacon, error) { + return nil, nil + }, + } + version, err := versionBeacon.ExecutionVersion() + require.NoError(t, err) + require.Equal(t, semver.Version{}, version) + }) + + t.Run("version beacon", func(t *testing.T) { + versionBeacon := NewVersionBeaconExecutionVersionProvider( + func() (*flow.SealedVersionBeacon, error) { + return &flow.SealedVersionBeacon{ + VersionBeacon: &flow.VersionBeacon{ + VersionBoundaries: []flow.VersionBoundary{ + { + BlockHeight: 10, + Version: semver.Version{Major: 0, Minor: 37, Patch: 0}.String(), + }, + }, + }, + }, nil + }, + ) + version, err := versionBeacon.ExecutionVersion() + require.NoError(t, err) + require.Equal(t, semver.Version{Major: 0, Minor: 37, Patch: 0}, version) + }) + + t.Run("version beacon, multiple boundaries", func(t *testing.T) { + versionBeacon := NewVersionBeaconExecutionVersionProvider( + func() (*flow.SealedVersionBeacon, error) { + return &flow.SealedVersionBeacon{ + VersionBeacon: &flow.VersionBeacon{ + VersionBoundaries: []flow.VersionBoundary{ + { + BlockHeight: 10, + Version: semver.Version{Major: 0, Minor: 37, Patch: 0}.String(), + }, + { + BlockHeight: 20, + Version: semver.Version{Major: 1, Minor: 0, Patch: 0}.String(), + }, + }, + }, + }, nil + }, + ) + + version, err := versionBeacon.ExecutionVersion() + require.NoError(t, err) + // the first boundary is by definition the newest past one and defines the version + require.Equal(t, semver.Version{Major: 0, Minor: 37, Patch: 0}, version) + }) +} + +func Test_MinimumCadenceRequiredVersion(t *testing.T) { + t.Run("no version beacon", func(t *testing.T) { + getCadenceVersion := func(executionVersion string) (string, error) { + versionBeacon := NewVersionBeaconExecutionVersionProvider( + func() (*flow.SealedVersionBeacon, error) { + return &flow.SealedVersionBeacon{ + VersionBeacon: &flow.VersionBeacon{ + VersionBoundaries: []flow.VersionBoundary{ + { + BlockHeight: 10, + Version: executionVersion, + }, + }, + }, + }, nil + }, + ) + cadenceVersion := NewMinimumCadenceRequiredVersion(versionBeacon) + return cadenceVersion.MinimumRequiredVersion() + } + + setFVMToCadenceVersionMappingForTestingOnly(FlowGoToCadenceVersionMapping{ + FlowGoVersion: semver.Version{Major: 0, Minor: 37, Patch: 0}, + CadenceVersion: semver.Version{Major: 1, Minor: 0, Patch: 0}, + }) + + requireExpectedSemver := func(t *testing.T, executionVersion semver.Version, expectedCadenceVersion semver.Version) { + t.Helper() + actualCadenceVersion, err := getCadenceVersion(executionVersion.String()) + require.NoError(t, err) + require.Equal(t, expectedCadenceVersion.String(), actualCadenceVersion) + } + + requireExpectedSemver(t, semver.Version{Major: 0, Minor: 36, Patch: 9}, semver.Version{Major: 0, Minor: 0, Patch: 0}) + requireExpectedSemver(t, semver.Version{Major: 0, Minor: 37, Patch: 0}, semver.Version{Major: 1, Minor: 0, Patch: 0}) + requireExpectedSemver(t, semver.Version{Major: 0, Minor: 37, Patch: 1}, semver.Version{Major: 1, Minor: 0, Patch: 0}) + }) +} diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index 39aaf310aea..d73e50437ec 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -907,36 +907,6 @@ func (_m *Environment) GetCurrentBlockHeight() (uint64, error) { return r0, r1 } -// GetCurrentVersionBoundary provides a mock function with given fields: -func (_m *Environment) GetCurrentVersionBoundary() (cadence.Value, error) { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for GetCurrentVersionBoundary") - } - - var r0 cadence.Value - var r1 error - if rf, ok := ret.Get(0).(func() (cadence.Value, error)); ok { - return rf() - } - if rf, ok := ret.Get(0).(func() cadence.Value); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cadence.Value) - } - } - - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // GetInterpreterSharedState provides a mock function with given fields: func (_m *Environment) GetInterpreterSharedState() *interpreter.SharedState { ret := _m.Called() diff --git a/fvm/environment/mock/execution_version_provider.go b/fvm/environment/mock/execution_version_provider.go new file mode 100644 index 00000000000..12a4df83ecb --- /dev/null +++ b/fvm/environment/mock/execution_version_provider.go @@ -0,0 +1,55 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + semver "github.com/coreos/go-semver/semver" + mock "github.com/stretchr/testify/mock" +) + +// ExecutionVersionProvider is an autogenerated mock type for the ExecutionVersionProvider type +type ExecutionVersionProvider struct { + mock.Mock +} + +// ExecutionVersion provides a mock function with given fields: +func (_m *ExecutionVersionProvider) ExecutionVersion() (semver.Version, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for ExecutionVersion") + } + + var r0 semver.Version + var r1 error + if rf, ok := ret.Get(0).(func() (semver.Version, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() semver.Version); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(semver.Version) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewExecutionVersionProvider creates a new instance of ExecutionVersionProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewExecutionVersionProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *ExecutionVersionProvider { + mock := &ExecutionVersionProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/fvm/environment/mock/get_version_beacon_func.go b/fvm/environment/mock/get_version_beacon_func.go new file mode 100644 index 00000000000..ffe09dd9ce6 --- /dev/null +++ b/fvm/environment/mock/get_version_beacon_func.go @@ -0,0 +1,57 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + flow "github.com/onflow/flow-go/model/flow" + mock "github.com/stretchr/testify/mock" +) + +// GetVersionBeaconFunc is an autogenerated mock type for the GetVersionBeaconFunc type +type GetVersionBeaconFunc struct { + mock.Mock +} + +// Execute provides a mock function with given fields: +func (_m *GetVersionBeaconFunc) Execute() (*flow.SealedVersionBeacon, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Execute") + } + + var r0 *flow.SealedVersionBeacon + var r1 error + if rf, ok := ret.Get(0).(func() (*flow.SealedVersionBeacon, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *flow.SealedVersionBeacon); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.SealedVersionBeacon) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewGetVersionBeaconFunc creates a new instance of GetVersionBeaconFunc. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewGetVersionBeaconFunc(t interface { + mock.TestingT + Cleanup(func()) +}) *GetVersionBeaconFunc { + mock := &GetVersionBeaconFunc{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/fvm/environment/programs_test.go b/fvm/environment/programs_test.go index b0368db49e0..e39d224c9d4 100644 --- a/fvm/environment/programs_test.go +++ b/fvm/environment/programs_test.go @@ -156,9 +156,7 @@ func Test_Programs(t *testing.T) { fvm.WithAuthorizationChecksEnabled(false), fvm.WithSequenceNumberCheckAndIncrementEnabled(false), fvm.WithCadenceLogging(true), - fvm.WithDerivedBlockData(derivedBlockData), - // disable reading version from node version beacon otherwise it loads an extra contract - fvm.WithReadVersionFromNodeVersionBeacon(false)) + fvm.WithDerivedBlockData(derivedBlockData)) var contractASnapshot *snapshot.ExecutionSnapshot var contractBSnapshot *snapshot.ExecutionSnapshot @@ -615,9 +613,7 @@ func Test_ProgramsDoubleCounting(t *testing.T) { fvm.WithSequenceNumberCheckAndIncrementEnabled(false), fvm.WithCadenceLogging(true), fvm.WithDerivedBlockData(derivedBlockData), - fvm.WithMetricsReporter(metrics), - // disable reading version from node version beacon otherwise it loads an extra contract - fvm.WithReadVersionFromNodeVersionBeacon(false)) + fvm.WithMetricsReporter(metrics)) t.Run("deploy contracts and ensure cache is empty", func(t *testing.T) { // deploy contract A diff --git a/fvm/environment/system_contracts.go b/fvm/environment/system_contracts.go index e8e9598aa3c..22860dd38ea 100644 --- a/fvm/environment/system_contracts.go +++ b/fvm/environment/system_contracts.go @@ -312,21 +312,3 @@ func (sys *SystemContracts) AccountsStorageCapacity( }, ) } - -var getCurrentVersionBoundarySpec = ContractFunctionSpec{ - AddressFromChain: ServiceAddress, - LocationName: systemcontracts.ContractNameNodeVersionBeacon, - FunctionName: systemcontracts.ContractVersionBeacon_getCurrentVersionBoundary, - ArgumentTypes: []sema.Type{}, -} - -// GetCurrentVersionBoundary executes the getCurrentVersionBoundary function on the NodeVersionBeacon contract. -// the function will return the version boundary (version, block height) that is currently in effect. -// the version boundary currently in effect is the highest one not above the current block height. -// if there is no existing version boundary lower than the current block height, the function will return version 0 and block height 0. -func (sys *SystemContracts) GetCurrentVersionBoundary() (cadence.Value, error) { - return sys.Invoke( - getCurrentVersionBoundarySpec, - []cadence.Value{}, - ) -} diff --git a/fvm/errors/codes.go b/fvm/errors/codes.go index c4648667e1e..b6b355b21a5 100644 --- a/fvm/errors/codes.go +++ b/fvm/errors/codes.go @@ -27,6 +27,7 @@ const ( FailureCodeDerivedDataCacheImplementationFailure FailureCode = 2008 FailureCodeRandomSourceFailure FailureCode = 2009 FailureCodeEVMFailure FailureCode = 2010 + FailureCodeExecutionVersionProvider FailureCode = 2011 // Deprecated: No longer used. FailureCodeMetaTransactionFailure FailureCode = 2100 ) diff --git a/fvm/errors/execution.go b/fvm/errors/execution.go index b484c805f93..ec2f1c3d3fd 100644 --- a/fvm/errors/execution.go +++ b/fvm/errors/execution.go @@ -106,6 +106,17 @@ func NewRandomSourceFailure( "implementation error in random source provider") } +// NewExecutionVersionProviderFailure indicates a irrecoverable failure in the execution +// version provider. +func NewExecutionVersionProviderFailure( + err error, +) CodedFailure { + return WrapCodedFailure( + FailureCodeExecutionVersionProvider, + err, + "Failure in execution version provider") +} + // NewComputationLimitExceededError constructs a new CodedError which indicates // that computation has exceeded its limit. func NewComputationLimitExceededError(limit uint64) CodedError { diff --git a/fvm/executionParameters.go b/fvm/executionParameters.go index db88f42ef5f..5d4fd4febdd 100644 --- a/fvm/executionParameters.go +++ b/fvm/executionParameters.go @@ -5,11 +5,8 @@ import ( "fmt" "math" - "github.com/coreos/go-semver/semver" "github.com/rs/zerolog" - "github.com/onflow/flow-go/model/convert" - "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/cadence" @@ -73,8 +70,7 @@ func getExecutionParameters( NewExecutionParametersComputer(log, ctx, txnState)) if err != nil { return state.ExecutionParameters{ - MeterParameters: meterParams, - ExecutionVersion: semver.Version{}, + MeterParameters: meterParams, }, nil, err } @@ -99,8 +95,7 @@ func getExecutionParameters( } return state.ExecutionParameters{ - MeterParameters: meterParams, - ExecutionVersion: executionParams.ExecutionVersion, + MeterParameters: meterParams, }, executionParamsStateRead, nil } @@ -216,15 +211,6 @@ func (computer ExecutionParametersComputer) getExecutionParameters() ( return overrides, err } - executionVersion, err := GetMinimumRequiredExecutionVersion(computer.log, computer.ctx, env) - err = setIfOk( - "execution version", - err, - func() { overrides.ExecutionVersion = executionVersion }) - if err != nil { - return overrides, err - } - return overrides, nil } @@ -357,40 +343,3 @@ func GetExecutionMemoryLimit( return uint64(memoryLimitRaw), nil } - -func GetMinimumRequiredExecutionVersion( - log zerolog.Logger, - ctx Context, - env environment.Environment, -) (semver.Version, error) { - if !ctx.ReadVersionFromNodeVersionBeacon { - return semver.Version{}, nil - } - - // the current version boundary defines a block height and a minimum required version that is required past that block height. - value, err := env.GetCurrentVersionBoundary() - - if err != nil { - return semver.Version{}, fmt.Errorf("could not get current version boundary: %w", err) - } - - boundary, err := convert.VersionBoundary(value) - - if err != nil { - return semver.Version{}, fmt.Errorf("could not parse current version boundary: %w", err) - } - - semVer, err := semver.NewVersion(boundary.Version) - if err != nil { - // This could be problematic, if the version is not a valid semver version. The NodeVersionBeacon should prevent - // this, but it could have bugs. - // Erroring here gives us no way to recover as no transactions would work anymore, - // instead return the version as 0.0.0 and log the error, allowing us to recover. - // this would mean that any if-statements that were relying on a higher version would fail, - // but that is preferable to all transactions halting. - log.Error().Err(err).Msg("could not parse version boundary. Version boundary as defined in the NodeVersionBeacon contract is not a valid semver version!") - return semver.Version{}, nil - } - - return *semVer, nil -} diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index eedc9e50c8b..eaa8bef6c85 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -9,9 +9,11 @@ import ( "math" "strings" "testing" - "time" - "github.com/coreos/go-semver/semver" + "github.com/stretchr/testify/assert" + mockery "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "github.com/onflow/cadence" "github.com/onflow/cadence/common" "github.com/onflow/cadence/encoding/ccf" @@ -23,13 +25,8 @@ import ( cadenceStdlib "github.com/onflow/cadence/stdlib" "github.com/onflow/cadence/test_utils/runtime_utils" "github.com/onflow/crypto" - "github.com/onflow/flow-core-contracts/lib/go/templates" flowsdk "github.com/onflow/flow-go-sdk" "github.com/onflow/flow-go-sdk/test" - "github.com/rs/zerolog" - "github.com/stretchr/testify/assert" - mockery "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" "github.com/onflow/flow-go/engine/execution/testutil" exeUtils "github.com/onflow/flow-go/engine/execution/utils" @@ -44,7 +41,6 @@ import ( "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" reusableRuntime "github.com/onflow/flow-go/fvm/runtime" - "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/snapshot/mock" "github.com/onflow/flow-go/fvm/storage/state" @@ -3400,196 +3396,6 @@ func TestCrypto(t *testing.T) { }) } -func Test_MinimumRequiredVersion(t *testing.T) { - - chain := flow.Emulator.Chain() - sc := systemcontracts.SystemContractsForChain(chain.ChainID()) - log := zerolog.New(zerolog.NewTestWriter(t)) - - getVersion := func(ctx fvm.Context, snapshotTree snapshot.SnapshotTree) string { - blockDatabase := storage.NewBlockDatabase( - snapshotTree, - 0, - nil) - - txnState, err := blockDatabase.NewTransaction(0, state.DefaultParameters()) - require.NoError(t, err) - - executionParams, _, err := txnState.GetStateExecutionParameters( - txnState, - fvm.NewExecutionParametersComputer(log, ctx, txnState)) - require.NoError(t, err) - - // this will set the parameters to the txnState. - // this is done at the beginning of a transaction/script - txnId, err := txnState.BeginNestedTransactionWithMeterParams( - state.ExecutionParameters{ - ExecutionVersion: executionParams.ExecutionVersion, - }) - require.NoError(t, err) - - mrv := environment.NewMinimumCadenceRequiredVersion(txnState) - - v, err := mrv.MinimumRequiredVersion() - - require.NoError(t, err) - _, err = txnState.CommitNestedTransaction(txnId) - require.NoError(t, err) - - return v - } - - insertVersionBoundary := func(newVersion semver.Version, currentHeight, insertHeight uint64, ctx fvm.Context, snapshotTree snapshot.SnapshotTree, vm fvm.VM, txIndex uint32) snapshot.SnapshotTree { - setVersionBoundaryScript := templates.GenerateSetVersionBoundaryScript(sc.AsTemplateEnv()) - tx := flow.NewTransactionBody(). - SetScript(setVersionBoundaryScript). - SetProposalKey(sc.FlowServiceAccount.Address, 0, 0). - AddAuthorizer(sc.FlowServiceAccount.Address). - SetPayer(sc.FlowServiceAccount.Address) - - tx. - AddArgument(jsoncdc.MustEncode(cadence.UInt8(newVersion.Major))). - AddArgument(jsoncdc.MustEncode(cadence.UInt8(newVersion.Minor))). - AddArgument(jsoncdc.MustEncode(cadence.UInt8(newVersion.Patch))). - AddArgument(jsoncdc.MustEncode(cadence.String(newVersion.PreRelease))) - - tx.AddArgument(jsoncdc.MustEncode(cadence.UInt64(insertHeight))) - - startHeader := flow.Header{ - Height: currentHeight, - ChainID: chain.ChainID(), - Timestamp: time.Now().UTC(), - } - - blocks := new(envMock.Blocks) - ctxWithBlock := fvm.NewContextFromParent( - ctx, - fvm.WithBlockHeader(&startHeader), - fvm.WithBlocks(blocks), - ) - - executionSnapshot, output, err := vm.Run( - ctxWithBlock, - fvm.Transaction(tx, txIndex), - snapshotTree) - - require.NoError(t, err) - require.NoError(t, output.Err) - return snapshotTree.Append(executionSnapshot) - } - - runSystemTxToUpdateNodeVersionBeaconContract := func(atHeight uint64, ctx fvm.Context, snapshotTree snapshot.SnapshotTree, vm fvm.VM, txIndex uint32) snapshot.SnapshotTree { - txBody := flow.NewTransactionBody(). - SetScript([]byte(fmt.Sprintf(` - import NodeVersionBeacon from %s - - transaction { - prepare(serviceAccount: auth(BorrowValue) &Account) { - - let versionBeaconHeartbeat = serviceAccount.storage - .borrow<&NodeVersionBeacon.Heartbeat>(from: NodeVersionBeacon.HeartbeatStoragePath) - ?? panic("Couldn't borrow NodeVersionBeacon.Heartbeat Resource") - versionBeaconHeartbeat.heartbeat() - } - } - `, - sc.NodeVersionBeacon.Address.HexWithPrefix()))). - SetProposalKey(sc.FlowServiceAccount.Address, 0, 0). - AddAuthorizer(sc.FlowServiceAccount.Address). - SetPayer(sc.FlowServiceAccount.Address) - - endHeader := flow.Header{ - Height: atHeight, - ChainID: chain.ChainID(), - Timestamp: time.Now().UTC(), - } - - blocks := new(envMock.Blocks) - ctxWithBlock := fvm.NewContextFromParent(ctx, - fvm.WithBlockHeader(&endHeader), - fvm.WithBlocks(blocks), - ) - - executionSnapshot, output, err := vm.Run( - ctxWithBlock, - fvm.Transaction(txBody, txIndex), - snapshotTree) - - require.NoError(t, err) - require.NoError(t, output.Err) - - return snapshotTree.Append(executionSnapshot) - } - - t.Run("minimum required version", newVMTest(). - withContextOptions( - fvm.WithChain(chain), - fvm.WithAuthorizationChecksEnabled(false), - fvm.WithSequenceNumberCheckAndIncrementEnabled(false), - ). - run(func( - t *testing.T, - vm fvm.VM, - chain flow.Chain, - ctx fvm.Context, - snapshotTree snapshot.SnapshotTree, - ) { - // default version is empty - require.Equal(t, semver.Version{}.String(), getVersion(ctx, snapshotTree)) - - // define mapping for flow go version to cadence version - flowVersion1 := semver.Version{ - Major: 1, - Minor: 2, - Patch: 3, - PreRelease: "rc.1", - } - cadenceVersion1 := semver.Version{ - Major: 2, - Minor: 1, - Patch: 3, - PreRelease: "rc.2", - } - environment.SetFVMToCadenceVersionMappingForTestingOnly( - environment.FlowGoToCadenceVersionMapping{ - FlowGoVersion: flowVersion1, - CadenceVersion: cadenceVersion1, - }) - - h0 := uint64(100) // starting height - hv1 := uint64(2000) // version boundary height - - txIndex := uint32(0) - - // insert version boundary 1 - snapshotTree = insertVersionBoundary(flowVersion1, h0, hv1, ctx, snapshotTree, vm, txIndex) - txIndex += 1 - - // so far no change: - require.Equal(t, semver.Version{}.String(), getVersion(ctx, snapshotTree)) - - // system transaction needs to run to update the flowVersion on chain - snapshotTree = runSystemTxToUpdateNodeVersionBeaconContract(hv1-1, ctx, snapshotTree, vm, txIndex) - txIndex += 1 - - // no change: - require.Equal(t, semver.Version{}.String(), getVersion(ctx, snapshotTree)) - - // system transaction needs to run to update the flowVersion on chain - snapshotTree = runSystemTxToUpdateNodeVersionBeaconContract(hv1, ctx, snapshotTree, vm, txIndex) - txIndex += 1 - - // switch to cadence version 1 - require.Equal(t, cadenceVersion1.String(), getVersion(ctx, snapshotTree)) - - // system transaction needs to run to update the flowVersion on chain - snapshotTree = runSystemTxToUpdateNodeVersionBeaconContract(hv1+1, ctx, snapshotTree, vm, txIndex) - - // still cadence version 1 - require.Equal(t, cadenceVersion1.String(), getVersion(ctx, snapshotTree)) - })) -} - func Test_BlockHashListShouldWriteOnPush(t *testing.T) { chain := flow.Emulator.Chain() diff --git a/fvm/storage/derived/invalidator.go b/fvm/storage/derived/invalidator.go index 9467c169bdc..6e7d2918fe8 100644 --- a/fvm/storage/derived/invalidator.go +++ b/fvm/storage/derived/invalidator.go @@ -1,7 +1,6 @@ package derived import ( - "github.com/coreos/go-semver/semver" "github.com/onflow/cadence/common" "github.com/onflow/flow-go/fvm/meter" @@ -16,7 +15,6 @@ type MeterParamOverrides struct { // StateExecutionParameters are parameters needed for execution defined in the execution state. type StateExecutionParameters struct { MeterParamOverrides - ExecutionVersion semver.Version } type ProgramInvalidator TableInvalidator[ diff --git a/fvm/storage/state/execution_state.go b/fvm/storage/state/execution_state.go index 12968e4b0a6..2a51c483643 100644 --- a/fvm/storage/state/execution_state.go +++ b/fvm/storage/state/execution_state.go @@ -4,8 +4,6 @@ import ( "fmt" "math" - "github.com/coreos/go-semver/semver" - "github.com/onflow/cadence/common" "github.com/onflow/crypto/hash" @@ -30,8 +28,7 @@ type ExecutionState struct { finalized bool *spockState - meter *meter.Meter - executionVersion semver.Version + meter *meter.Meter // NOTE: parent and child state shares the same limits controller *limitsController @@ -46,7 +43,6 @@ type StateParameters struct { type ExecutionParameters struct { meter.MeterParameters - ExecutionVersion semver.Version } func DefaultParameters() StateParameters { @@ -144,7 +140,6 @@ func (state *ExecutionState) NewChildWithMeterParams( finalized: false, spockState: state.spockState.NewChild(), meter: meter.NewMeter(params.MeterParameters), - executionVersion: params.ExecutionVersion, limitsController: state.limitsController, } } @@ -363,8 +358,7 @@ func (state *ExecutionState) checkSize( func (state *ExecutionState) ExecutionParameters() ExecutionParameters { return ExecutionParameters{ - MeterParameters: state.meter.MeterParameters, - ExecutionVersion: state.executionVersion, + MeterParameters: state.meter.MeterParameters, } } diff --git a/fvm/systemcontracts/system_contracts.go b/fvm/systemcontracts/system_contracts.go index ad9f66c4a65..3760044698e 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -65,7 +65,6 @@ const ( ContractStorageFeesFunction_calculateAccountCapacity = "calculateAccountCapacity" ContractStorageFeesFunction_getAccountsCapacityForTransactionStorageCheck = "getAccountsCapacityForTransactionStorageCheck" ContractStorageFeesFunction_defaultTokenAvailableBalance = "defaultTokenAvailableBalance" - ContractVersionBeacon_getCurrentVersionBoundary = "getCurrentVersionBoundary" // These are the account indexes of system contracts as deployed by the default bootstrapping. // On long-running networks some of these contracts might have been deployed after bootstrapping, diff --git a/fvm/transactionInvoker.go b/fvm/transactionInvoker.go index 893687ee548..de2fc63c1a9 100644 --- a/fvm/transactionInvoker.go +++ b/fvm/transactionInvoker.go @@ -34,8 +34,6 @@ type TransactionExecutorParams struct { // Note: This is disabled only by tests TransactionBodyExecutionEnabled bool - - ReadVersionFromNodeVersionBeacon bool } func DefaultTransactionExecutorParams() TransactionExecutorParams { @@ -44,7 +42,6 @@ func DefaultTransactionExecutorParams() TransactionExecutorParams { SequenceNumberCheckAndIncrementEnabled: true, AccountKeyWeightThreshold: AccountKeyWeightThreshold, TransactionBodyExecutionEnabled: true, - ReadVersionFromNodeVersionBeacon: true, } } diff --git a/insecure/wintermute/attackOrchestrator.go b/insecure/wintermute/attackOrchestrator.go index 9ff3bd3c1f5..1f90f7f0a3f 100644 --- a/insecure/wintermute/attackOrchestrator.go +++ b/insecure/wintermute/attackOrchestrator.go @@ -144,14 +144,14 @@ func (o *Orchestrator) corruptExecutionResult(receipt *flow.ExecutionReceipt) *f BlockID: receipt.ExecutionResult.BlockID, // replace all chunks with new ones to simulate chunk corruption Chunks: flow.ChunkList{ - unittest.ChunkFixture(receipt.ExecutionResult.BlockID, 0, unittest.WithChunkStartState(receiptStartState)), + unittest.ChunkFixture(receipt.ExecutionResult.BlockID, 0, receiptStartState), }, ServiceEvents: receipt.ExecutionResult.ServiceEvents, ExecutionDataID: receipt.ExecutionResult.ExecutionDataID, } if chunksNum > 1 { - result.Chunks = append(result.Chunks, unittest.ChunkListFixture(uint(chunksNum-1), receipt.ExecutionResult.BlockID)...) + result.Chunks = append(result.Chunks, unittest.ChunkListFixture(uint(chunksNum-1), receipt.ExecutionResult.BlockID, result.Chunks[0].EndState)...) } return result diff --git a/integration/tests/access/cohort4/execution_data_pruning_test.go b/integration/tests/access/cohort4/execution_data_pruning_test.go index b3b4205d8a5..81c830c7a72 100644 --- a/integration/tests/access/cohort4/execution_data_pruning_test.go +++ b/integration/tests/access/cohort4/execution_data_pruning_test.go @@ -85,7 +85,7 @@ func (s *ExecutionDataPruningSuite) SetupTest() { testnet.WithAdditionalFlagf("--event-query-mode=local-only"), testnet.WithAdditionalFlagf("--execution-data-height-range-target=%d", s.heightRangeTarget), testnet.WithAdditionalFlagf("--execution-data-height-range-threshold=%d", s.threshold), - testnet.WithAdditionalFlagf(fmt.Sprintf("--execution-data-pruning-interval=%s", s.pruningInterval)), + testnet.WithAdditionalFlagf("--execution-data-pruning-interval=%s", s.pruningInterval), ) consensusConfigs := []func(config *testnet.NodeConfig){ diff --git a/model/flow/chunk_test.go b/model/flow/chunk_test.go index 9da330dcaaa..0c1cbe7729f 100644 --- a/model/flow/chunk_test.go +++ b/model/flow/chunk_test.go @@ -62,7 +62,7 @@ func TestDistinctChunkIDs_FullChunks(t *testing.T) { require.NotEqual(t, blockIdA, blockIdB) // generates a chunk associated with blockA - chunkA := unittest.ChunkFixture(blockIdA, 42) + chunkA := unittest.ChunkFixture(blockIdA, 42, unittest.StateCommitmentFixture()) // generates a deep copy of chunkA in chunkB chunkB := *chunkA @@ -80,7 +80,7 @@ func TestDistinctChunkIDs_FullChunks(t *testing.T) { // TestChunkList_Indices evaluates the Indices method of ChunkList on lists of different sizes. func TestChunkList_Indices(t *testing.T) { - cl := unittest.ChunkListFixture(5, unittest.IdentifierFixture()) + cl := unittest.ChunkListFixture(5, unittest.IdentifierFixture(), unittest.StateCommitmentFixture()) t.Run("empty chunk subset indices", func(t *testing.T) { // subset of chunk list that is empty should return an empty list subset := flow.ChunkList{} diff --git a/model/verification/convert/convert.go b/model/verification/convert/convert.go index 4e62e4d446c..3ffbcd3a8d8 100644 --- a/model/verification/convert/convert.go +++ b/model/verification/convert/convert.go @@ -12,7 +12,7 @@ func FromChunkDataPack( chunk *flow.Chunk, chunkDataPack *flow.ChunkDataPack, header *flow.Header, - snapshot protocol.Snapshot, + snapshot protocol.SnapshotExecutionSubset, result *flow.ExecutionResult, ) (*verification.VerifiableChunkData, error) { diff --git a/model/verification/verifiableChunkData.go b/model/verification/verifiableChunkData.go index 2f6f1e22579..ec2ec448351 100644 --- a/model/verification/verifiableChunkData.go +++ b/model/verification/verifiableChunkData.go @@ -8,12 +8,12 @@ import ( // VerifiableChunkData represents a ready-to-verify chunk // It contains the execution result as well as all resources needed to verify it type VerifiableChunkData struct { - IsSystemChunk bool // indicates whether this is a system chunk - Chunk *flow.Chunk // the chunk to be verified - Header *flow.Header // BlockHeader that contains this chunk - Snapshot protocol.Snapshot // state snapshot at the chunk's block - Result *flow.ExecutionResult // execution result of this block - ChunkDataPack *flow.ChunkDataPack // chunk data package needed to verify this chunk - EndState flow.StateCommitment // state commitment at the end of this chunk - TransactionOffset uint32 // index of the first transaction in a chunk within a block + IsSystemChunk bool // indicates whether this is a system chunk + Chunk *flow.Chunk // the chunk to be verified + Header *flow.Header // BlockHeader that contains this chunk + Snapshot protocol.SnapshotExecutionSubset // state snapshot at the chunk's block + Result *flow.ExecutionResult // execution result of this block + ChunkDataPack *flow.ChunkDataPack // chunk data package needed to verify this chunk + EndState flow.StateCommitment // state commitment at the end of this chunk + TransactionOffset uint32 // index of the first transaction in a chunk within a block } diff --git a/module/chunks/chunkVerifier.go b/module/chunks/chunkVerifier.go index e71bc8b63e8..53f30928511 100644 --- a/module/chunks/chunkVerifier.go +++ b/module/chunks/chunkVerifier.go @@ -59,13 +59,7 @@ func (fcv *ChunkVerifier) Verify( ctx = fvm.NewContextFromParent( fcv.systemChunkCtx, fvm.WithBlockHeader(vc.Header), - // `protocol.Snapshot` implements `EntropyProvider` interface - // Note that `Snapshot` possible errors for RandomSource() are: - // - storage.ErrNotFound if the QC is unknown. - // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown - // However, at this stage, snapshot reference block should be known and the QC should also be known, - // so no error is expected in normal operations, as required by `EntropyProvider`. - fvm.WithEntropyProvider(vc.Snapshot), + fvm.WithProtocolStateSnapshot(vc.Snapshot), ) txBody, err := blueprints.SystemChunkTransaction(fcv.vmCtx.Chain) @@ -80,13 +74,7 @@ func (fcv *ChunkVerifier) Verify( ctx = fvm.NewContextFromParent( fcv.vmCtx, fvm.WithBlockHeader(vc.Header), - // `protocol.Snapshot` implements `EntropyProvider` interface - // Note that `Snapshot` possible errors for RandomSource() are: - // - storage.ErrNotFound if the QC is unknown. - // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown - // However, at this stage, snapshot reference block should be known and the QC should also be known, - // so no error is expected in normal operations, as required by `EntropyProvider`. - fvm.WithEntropyProvider(vc.Snapshot), + fvm.WithProtocolStateSnapshot(vc.Snapshot), ) transactions = make( diff --git a/module/chunks/chunkVerifier_test.go b/module/chunks/chunkVerifier_test.go index 4ea50cb3fed..89c709517be 100644 --- a/module/chunks/chunkVerifier_test.go +++ b/module/chunks/chunkVerifier_test.go @@ -666,5 +666,18 @@ func (m *testMetadata) RefreshChunkData(t *testing.T) *verification.VerifiableCh Result: result, ChunkDataPack: chunkDataPack, EndState: flow.StateCommitment(endState), + Snapshot: mockSnapshotSubset{}, } } + +type mockSnapshotSubset struct{} + +func (m mockSnapshotSubset) RandomSource() ([]byte, error) { + //TODO implement me + panic("implement me") +} + +func (m mockSnapshotSubset) VersionBeacon() (*flow.SealedVersionBeacon, error) { + //TODO implement me + panic("implement me") +} diff --git a/module/execution/scripts.go b/module/execution/scripts.go index 640353f36f0..0bb337b5f56 100644 --- a/module/execution/scripts.go +++ b/module/execution/scripts.go @@ -13,6 +13,7 @@ import ( "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/model/flow" "github.com/onflow/flow-go/module" + "github.com/onflow/flow-go/state/protocol" "github.com/onflow/flow-go/storage" ) @@ -75,7 +76,7 @@ func NewScripts( log zerolog.Logger, metrics module.ExecutionMetrics, chainID flow.ChainID, - entropy query.EntropyProviderPerBlock, + protocolSnapshotProvider protocol.SnapshotExecutionSubsetProvider, header storage.Headers, registerAtHeight RegisterAtHeight, queryConf query.QueryConfig, @@ -98,7 +99,7 @@ func NewScripts( vm, vmCtx, derivedChainData, - entropy, + protocolSnapshotProvider, ) return &Scripts{ diff --git a/module/execution/scripts_test.go b/module/execution/scripts_test.go index e02d73f3dce..4f86e3fe2cf 100644 --- a/module/execution/scripts_test.go +++ b/module/execution/scripts_test.go @@ -11,12 +11,10 @@ import ( jsoncdc "github.com/onflow/cadence/encoding/json" "github.com/onflow/cadence/stdlib" "github.com/rs/zerolog" - mocks "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/onflow/flow-go/engine/execution/computation/query" - "github.com/onflow/flow-go/engine/execution/computation/query/mock" "github.com/onflow/flow-go/engine/execution/testutil" "github.com/onflow/flow-go/fvm" "github.com/onflow/flow-go/fvm/errors" @@ -156,7 +154,7 @@ func (s *scriptTestSuite) TestGetAccountKeys() { func (s *scriptTestSuite) SetupTest() { logger := unittest.LoggerForTest(s.Suite.T(), zerolog.InfoLevel) - entropyProvider := testutil.EntropyProviderFixture(nil) + entropyProvider := testutil.ProtocolStateWithSourceFixture(nil) blockchain := unittest.BlockchainFixture(10) headers := newBlockHeadersStorage(blockchain) @@ -170,12 +168,6 @@ func (s *scriptTestSuite) SetupTest() { ) s.height = blockchain[0].Header.Height - entropyBlock := mock.NewEntropyProviderPerBlock(s.T()) - entropyBlock. - On("AtBlockID", mocks.AnythingOfType("flow.Identifier")). - Return(entropyProvider). - Maybe() - s.dbDir = unittest.TempDir(s.T()) db := pebbleStorage.NewBootstrappedRegistersWithPathForTest(s.T(), s.dbDir, s.height, s.height) pebbleRegisters, err := pebbleStorage.NewRegisters(db, pebbleStorage.PruningDisabled) @@ -205,7 +197,7 @@ func (s *scriptTestSuite) SetupTest() { logger, metrics.NewNoopCollector(), s.chain.ChainID(), - entropyBlock, + entropyProvider, headers, index.RegisterValue, query.NewDefaultConfig(), diff --git a/module/mempool/errors.go b/module/mempool/errors.go index 8c7ffbc3632..1dec04c84f8 100644 --- a/module/mempool/errors.go +++ b/module/mempool/errors.go @@ -10,10 +10,6 @@ type UnknownExecutionResultError struct { err error } -func NewUnknownExecutionResultError(msg string) error { - return NewUnknownExecutionResultErrorf(msg) -} - func NewUnknownExecutionResultErrorf(msg string, args ...interface{}) error { return UnknownExecutionResultError{ err: fmt.Errorf(msg, args...), diff --git a/module/validation/receipt_validator.go b/module/validation/receipt_validator.go index 9fb2569e21c..947d05cf166 100644 --- a/module/validation/receipt_validator.go +++ b/module/validation/receipt_validator.go @@ -96,6 +96,14 @@ func (v *receiptValidator) verifyChunksFormat(result *flow.ExecutionResult) erro if result.Chunks.Len() != requiredChunks { return engine.NewInvalidInputErrorf("invalid number of chunks, expected %d got %d", requiredChunks, result.Chunks.Len()) } + + // We have at least one chunk, check chunk state consistency + chunks := result.Chunks.Items() + for i := range len(chunks) - 1 { + if chunks[i].EndState != chunks[i+1].StartState { + return engine.NewInvalidInputErrorf("chunk state mismatch at index %v, EndState %v but next StartState %v", i, chunks[i].EndState, chunks[i+1].StartState) + } + } return nil } diff --git a/module/validation/receipt_validator_test.go b/module/validation/receipt_validator_test.go index 8d953978d50..93606ad1a94 100644 --- a/module/validation/receipt_validator_test.go +++ b/module/validation/receipt_validator_test.go @@ -224,6 +224,26 @@ func (s *ReceiptValidationSuite) TestReceiptForBlockWith0Collections() { }) } +// TestReceiptInconsistentChunkList tests that we reject receipts when the Start and End states +// within the chunk list are inconsistent (e.g. chunk[0].EndState != chunk[1].StartState). +func (s *ReceiptValidationSuite) TestReceiptInconsistentChunkList() { + s.publicKey.On("Verify", mock.Anything, mock.Anything, mock.Anything).Return(true, nil).Maybe() + valSubgrph := s.ValidSubgraphFixture() + chunks := valSubgrph.Result.Chunks + require.GreaterOrEqual(s.T(), chunks.Len(), 1) + // swap last chunk's start and end states + lastChunk := chunks[len(chunks)-1] + lastChunk.StartState, lastChunk.EndState = lastChunk.EndState, lastChunk.StartState + + receipt := unittest.ExecutionReceiptFixture(unittest.WithExecutorID(s.ExeID), + unittest.WithResult(valSubgrph.Result)) + s.AddSubgraphFixtureToMempools(valSubgrph) + + err := s.receiptValidator.Validate(receipt) + s.Require().Error(err, "should reject with invalid chunks") + s.Assert().True(engine.IsInvalidInputError(err)) +} + // TestReceiptTooManyChunks tests that we reject receipt with more chunks than expected func (s *ReceiptValidationSuite) TestReceiptTooManyChunks() { valSubgrph := s.ValidSubgraphFixture() diff --git a/module/validation/seal_validator.go b/module/validation/seal_validator.go index 31e5cd1ac53..f396268ba62 100644 --- a/module/validation/seal_validator.go +++ b/module/validation/seal_validator.go @@ -131,7 +131,7 @@ func (s *sealValidator) Validate(candidate *flow.Block) (*flow.Seal, error) { byBlock[seal.BlockID] = seal } if len(payload.Seals) != len(byBlock) { - return nil, engine.NewInvalidInputError("multiple seals for the same block") + return nil, engine.NewInvalidInputErrorf("multiple seals for the same block") } // incorporatedResults collects execution results that are incorporated in unsealed diff --git a/state/errors.go b/state/errors.go index d6997435df3..495aed5a4fe 100644 --- a/state/errors.go +++ b/state/errors.go @@ -19,10 +19,6 @@ type InvalidExtensionError struct { error } -func NewInvalidExtensionError(msg string) error { - return NewInvalidExtensionErrorf(msg) -} - func NewInvalidExtensionErrorf(msg string, args ...interface{}) error { return InvalidExtensionError{ error: fmt.Errorf(msg, args...), @@ -46,10 +42,6 @@ type OutdatedExtensionError struct { error } -func NewOutdatedExtensionError(msg string) error { - return NewOutdatedExtensionErrorf(msg) -} - func NewOutdatedExtensionErrorf(msg string, args ...interface{}) error { return OutdatedExtensionError{ error: fmt.Errorf(msg, args...), diff --git a/state/protocol/badger/mutator.go b/state/protocol/badger/mutator.go index e2fc5852322..6abf946c702 100644 --- a/state/protocol/badger/mutator.go +++ b/state/protocol/badger/mutator.go @@ -308,7 +308,7 @@ func (m *FollowerState) headerExtend(ctx context.Context, candidate *flow.Block, // STEP 1: Check that the payload is consistent with the payload hash in the header if candidate.Payload.Hash() != header.PayloadHash { - return state.NewInvalidExtensionError("payload integrity check failed") + return state.NewInvalidExtensionErrorf("payload integrity check failed") } // STEP 2: check whether the candidate (i) connects to the known block tree and diff --git a/state/protocol/badger/mutator_test.go b/state/protocol/badger/mutator_test.go index cc73140d6cd..b4a17e1c33b 100644 --- a/state/protocol/badger/mutator_test.go +++ b/state/protocol/badger/mutator_test.go @@ -719,7 +719,7 @@ func TestExtendReceiptsInvalid(t *testing.T) { require.NoError(t, err) // but receipt for block 2 is invalid, which the ParticipantState should reject with an InvalidExtensionError - validator.On("ValidatePayload", block3).Return(engine.NewInvalidInputError("")).Once() + validator.On("ValidatePayload", block3).Return(engine.NewInvalidInputErrorf("")).Once() err = state.Extend(context.Background(), block3) require.Error(t, err) require.True(t, st.IsInvalidExtensionError(err), err) @@ -2611,7 +2611,7 @@ func TestExtendInvalidSealsInBlock(t *testing.T) { return seal }, func(candidate *flow.Block) error { if candidate.ID() == block3.ID() { - return engine.NewInvalidInputError("") + return engine.NewInvalidInputErrorf("") } _, err := all.Seals.HighestInFork(candidate.Header.ParentID) return err diff --git a/state/protocol/execution.go b/state/protocol/execution.go new file mode 100644 index 00000000000..bae36c83706 --- /dev/null +++ b/state/protocol/execution.go @@ -0,0 +1,40 @@ +package protocol + +import "github.com/onflow/flow-go/model/flow" + +// SnapshotExecutionSubset is a subset of the protocol state snapshot that is needed by the FVM +// for execution. +type SnapshotExecutionSubset interface { + // RandomSource provides a source of entropy that can be + // expanded into randoms (using a pseudo-random generator). + // The returned slice should have at least 128 bits of entropy. + // The function doesn't error in normal operations, any + // error should be treated as an exception. + // + // `protocol.SnapshotExecutionSubset` implements `EntropyProvider` interface + // Note that `SnapshotExecutionSubset` possible errors for RandomSource() are: + // - storage.ErrNotFound if the QC is unknown. + // - state.ErrUnknownSnapshotReference if the snapshot reference block is unknown + // However, at this stage, snapshot reference block should be known and the QC should also be known, + // so no error is expected in normal operations, as required by `EntropyProvider`. + RandomSource() ([]byte, error) + + // VersionBeacon returns the latest sealed version beacon. + // If no version beacon has been sealed so far during the current spork, returns nil. + // The latest VersionBeacon is only updated for finalized blocks. This means that, when + // querying an un-finalized fork, `VersionBeacon` will have the same value as querying + // the snapshot for the latest finalized block, even if a newer version beacon is included + // in a seal along the un-finalized fork. + // + // The SealedVersionBeacon must contain at least one entry. The first entry is for a past block height. + // The remaining entries are for all future block heights. Future version boundaries + // can be removed, in which case the emitted event will not contain the removed version + // boundaries. + VersionBeacon() (*flow.SealedVersionBeacon, error) +} + +// SnapshotExecutionSubsetProvider is an interface that provides a subset of the protocol state +// at a specific block. +type SnapshotExecutionSubsetProvider interface { + AtBlockID(blockID flow.Identifier) SnapshotExecutionSubset +} diff --git a/state/protocol/mock/snapshot_execution_subset.go b/state/protocol/mock/snapshot_execution_subset.go new file mode 100644 index 00000000000..1950f9adace --- /dev/null +++ b/state/protocol/mock/snapshot_execution_subset.go @@ -0,0 +1,87 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + flow "github.com/onflow/flow-go/model/flow" + mock "github.com/stretchr/testify/mock" +) + +// SnapshotExecutionSubset is an autogenerated mock type for the SnapshotExecutionSubset type +type SnapshotExecutionSubset struct { + mock.Mock +} + +// RandomSource provides a mock function with given fields: +func (_m *SnapshotExecutionSubset) RandomSource() ([]byte, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for RandomSource") + } + + var r0 []byte + var r1 error + if rf, ok := ret.Get(0).(func() ([]byte, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() []byte); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// VersionBeacon provides a mock function with given fields: +func (_m *SnapshotExecutionSubset) VersionBeacon() (*flow.SealedVersionBeacon, error) { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for VersionBeacon") + } + + var r0 *flow.SealedVersionBeacon + var r1 error + if rf, ok := ret.Get(0).(func() (*flow.SealedVersionBeacon, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() *flow.SealedVersionBeacon); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*flow.SealedVersionBeacon) + } + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSnapshotExecutionSubset creates a new instance of SnapshotExecutionSubset. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSnapshotExecutionSubset(t interface { + mock.TestingT + Cleanup(func()) +}) *SnapshotExecutionSubset { + mock := &SnapshotExecutionSubset{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/state/protocol/mock/snapshot_execution_subset_provider.go b/state/protocol/mock/snapshot_execution_subset_provider.go new file mode 100644 index 00000000000..445c6daa7f5 --- /dev/null +++ b/state/protocol/mock/snapshot_execution_subset_provider.go @@ -0,0 +1,49 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + flow "github.com/onflow/flow-go/model/flow" + mock "github.com/stretchr/testify/mock" + + protocol "github.com/onflow/flow-go/state/protocol" +) + +// SnapshotExecutionSubsetProvider is an autogenerated mock type for the SnapshotExecutionSubsetProvider type +type SnapshotExecutionSubsetProvider struct { + mock.Mock +} + +// AtBlockID provides a mock function with given fields: blockID +func (_m *SnapshotExecutionSubsetProvider) AtBlockID(blockID flow.Identifier) protocol.SnapshotExecutionSubset { + ret := _m.Called(blockID) + + if len(ret) == 0 { + panic("no return value specified for AtBlockID") + } + + var r0 protocol.SnapshotExecutionSubset + if rf, ok := ret.Get(0).(func(flow.Identifier) protocol.SnapshotExecutionSubset); ok { + r0 = rf(blockID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(protocol.SnapshotExecutionSubset) + } + } + + return r0 +} + +// NewSnapshotExecutionSubsetProvider creates a new instance of SnapshotExecutionSubsetProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewSnapshotExecutionSubsetProvider(t interface { + mock.TestingT + Cleanup(func()) +}) *SnapshotExecutionSubsetProvider { + mock := &SnapshotExecutionSubsetProvider{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/storage/badger/procedure/executed.go b/storage/badger/procedure/executed.go index eb6a094f638..dfa1591c717 100644 --- a/storage/badger/procedure/executed.go +++ b/storage/badger/procedure/executed.go @@ -11,28 +11,10 @@ import ( "github.com/onflow/flow-go/storage/badger/operation" ) -// UpdateHighestExecutedBlockIfHigher updates the latest executed block to be the input block -// if the input block has a greater height than the currently stored latest executed block. -// The executed block index must have been initialized before calling this function. -// Returns storage.ErrNotFound if the input block does not exist in storage. -func UpdateHighestExecutedBlockIfHigher(header *flow.Header) func(txn *badger.Txn) error { +// UpdateLastExecutedBlock updates the latest executed block to be the input block +func UpdateLastExecutedBlock(header *flow.Header) func(txn *badger.Txn) error { return func(txn *badger.Txn) error { - var blockID flow.Identifier - err := operation.RetrieveExecutedBlock(&blockID)(txn) - if err != nil { - return fmt.Errorf("cannot lookup executed block: %w", err) - } - - var highest flow.Header - err = operation.RetrieveHeader(blockID, &highest)(txn) - if err != nil { - return fmt.Errorf("cannot retrieve executed header: %w", err) - } - - if header.Height <= highest.Height { - return nil - } - err = operation.UpdateExecutedBlock(header.ID())(txn) + err := operation.UpdateExecutedBlock(header.ID())(txn) if err != nil { return fmt.Errorf("cannot update highest executed block: %w", err) } @@ -41,9 +23,9 @@ func UpdateHighestExecutedBlockIfHigher(header *flow.Header) func(txn *badger.Tx } } -// GetHighestExecutedBlock retrieves the height and ID of the latest block executed by this node. +// GetLastExecutedBlock retrieves the height and ID of the latest block executed by this node. // Returns storage.ErrNotFound if no latest executed block has been stored. -func GetHighestExecutedBlock(height *uint64, blockID *flow.Identifier) func(tx *badger.Txn) error { +func GetLastExecutedBlock(height *uint64, blockID *flow.Identifier) func(tx *badger.Txn) error { return func(tx *badger.Txn) error { var highest flow.Header err := operation.RetrieveExecutedBlock(blockID)(tx) diff --git a/storage/badger/procedure/executed_test.go b/storage/badger/procedure/executed_test.go index ba776c17d97..0f2f68b0012 100644 --- a/storage/badger/procedure/executed_test.go +++ b/storage/badger/procedure/executed_test.go @@ -27,7 +27,7 @@ func TestInsertExecuted(t *testing.T) { var height uint64 var blockID flow.Identifier require.NoError(t, - db.View(GetHighestExecutedBlock(&height, &blockID)), + db.View(GetLastExecutedBlock(&height, &blockID)), ) require.Equal(t, root.ID(), blockID) @@ -37,13 +37,13 @@ func TestInsertExecuted(t *testing.T) { t.Run("insert and get", func(t *testing.T) { header1 := chain[1].Header require.NoError(t, - db.Update(UpdateHighestExecutedBlockIfHigher(header1)), + db.Update(UpdateLastExecutedBlock(header1)), ) var height uint64 var blockID flow.Identifier require.NoError(t, - db.View(GetHighestExecutedBlock(&height, &blockID)), + db.View(GetLastExecutedBlock(&height, &blockID)), ) require.Equal(t, header1.ID(), blockID) @@ -54,15 +54,15 @@ func TestInsertExecuted(t *testing.T) { header2 := chain[2].Header header3 := chain[3].Header require.NoError(t, - db.Update(UpdateHighestExecutedBlockIfHigher(header2)), + db.Update(UpdateLastExecutedBlock(header2)), ) require.NoError(t, - db.Update(UpdateHighestExecutedBlockIfHigher(header3)), + db.Update(UpdateLastExecutedBlock(header3)), ) var height uint64 var blockID flow.Identifier require.NoError(t, - db.View(GetHighestExecutedBlock(&height, &blockID)), + db.View(GetLastExecutedBlock(&height, &blockID)), ) require.Equal(t, header3.ID(), blockID) @@ -73,19 +73,19 @@ func TestInsertExecuted(t *testing.T) { header5 := chain[5].Header header4 := chain[4].Header require.NoError(t, - db.Update(UpdateHighestExecutedBlockIfHigher(header5)), + db.Update(UpdateLastExecutedBlock(header5)), ) require.NoError(t, - db.Update(UpdateHighestExecutedBlockIfHigher(header4)), + db.Update(UpdateLastExecutedBlock(header4)), ) var height uint64 var blockID flow.Identifier require.NoError(t, - db.View(GetHighestExecutedBlock(&height, &blockID)), + db.View(GetLastExecutedBlock(&height, &blockID)), ) - require.Equal(t, header5.ID(), blockID) - require.Equal(t, header5.Height, height) + require.Equal(t, header4.ID(), blockID) + require.Equal(t, header4.Height, height) }) }) } diff --git a/storage/mock/batch.go b/storage/mock/batch.go new file mode 100644 index 00000000000..371ffbf335c --- /dev/null +++ b/storage/mock/batch.go @@ -0,0 +1,90 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + storage "github.com/onflow/flow-go/storage" + mock "github.com/stretchr/testify/mock" +) + +// Batch is an autogenerated mock type for the Batch type +type Batch struct { + mock.Mock +} + +// AddCallback provides a mock function with given fields: _a0 +func (_m *Batch) AddCallback(_a0 func(error)) { + _m.Called(_a0) +} + +// Commit provides a mock function with given fields: +func (_m *Batch) Commit() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Commit") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GlobalReader provides a mock function with given fields: +func (_m *Batch) GlobalReader() storage.Reader { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for GlobalReader") + } + + var r0 storage.Reader + if rf, ok := ret.Get(0).(func() storage.Reader); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(storage.Reader) + } + } + + return r0 +} + +// Writer provides a mock function with given fields: +func (_m *Batch) Writer() storage.Writer { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Writer") + } + + var r0 storage.Writer + if rf, ok := ret.Get(0).(func() storage.Writer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(storage.Writer) + } + } + + return r0 +} + +// NewBatch creates a new instance of Batch. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBatch(t interface { + mock.TestingT + Cleanup(func()) +}) *Batch { + mock := &Batch{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/storage/mock/db.go b/storage/mock/db.go new file mode 100644 index 00000000000..561ca2bada0 --- /dev/null +++ b/storage/mock/db.go @@ -0,0 +1,85 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + storage "github.com/onflow/flow-go/storage" + mock "github.com/stretchr/testify/mock" +) + +// DB is an autogenerated mock type for the DB type +type DB struct { + mock.Mock +} + +// NewBatch provides a mock function with given fields: +func (_m *DB) NewBatch() storage.Batch { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for NewBatch") + } + + var r0 storage.Batch + if rf, ok := ret.Get(0).(func() storage.Batch); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(storage.Batch) + } + } + + return r0 +} + +// Reader provides a mock function with given fields: +func (_m *DB) Reader() storage.Reader { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Reader") + } + + var r0 storage.Reader + if rf, ok := ret.Get(0).(func() storage.Reader); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(storage.Reader) + } + } + + return r0 +} + +// WithReaderBatchWriter provides a mock function with given fields: _a0 +func (_m *DB) WithReaderBatchWriter(_a0 func(storage.ReaderBatchWriter) error) error { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for WithReaderBatchWriter") + } + + var r0 error + if rf, ok := ret.Get(0).(func(func(storage.ReaderBatchWriter) error) error); ok { + r0 = rf(_a0) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewDB creates a new instance of DB. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDB(t interface { + mock.TestingT + Cleanup(func()) +}) *DB { + mock := &DB{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/utils/grpcutils/grpc.go b/utils/grpcutils/grpc.go index e8c4404886c..40dcd5de142 100644 --- a/utils/grpcutils/grpc.go +++ b/utils/grpcutils/grpc.go @@ -141,7 +141,7 @@ func verifyPeerCertificateFunc(expectedPublicKey crypto.PublicKey) (func(rawCert for i := 0; i < len(rawCerts); i++ { cert, err := x509.ParseCertificate(rawCerts[i]) if err != nil { - return newServerAuthError(err.Error()) + return newServerAuthError("failed to parse certificate: %s", err.Error()) } chain[i] = cert } @@ -150,7 +150,7 @@ func verifyPeerCertificateFunc(expectedPublicKey crypto.PublicKey) (func(rawCert // extension, extract the remote's public key and finally verifies the signature included in the certificate actualLibP2PKey, err := libp2ptls.PubKeyFromCertChain(chain) if err != nil { - return newServerAuthError(err.Error()) + return newServerAuthError("could not convert certificate to libp2p public key: %s", err.Error()) } // verify that the public key received is the one that is expected @@ -170,7 +170,7 @@ func verifyPeerCertificateFunc(expectedPublicKey crypto.PublicKey) (func(rawCert func libP2PKeyToHexString(key lcrypto.PubKey) (string, *ServerAuthError) { keyRaw, err := key.Raw() if err != nil { - return "", newServerAuthError(err.Error()) + return "", newServerAuthError("could not convert public key to hex string: %s", err.Error()) } return hex.EncodeToString(keyRaw), nil } diff --git a/utils/unittest/fixtures.go b/utils/unittest/fixtures.go index 9c3f9784493..de86955d25d 100644 --- a/utils/unittest/fixtures.go +++ b/utils/unittest/fixtures.go @@ -893,15 +893,14 @@ func WithBlock(block *flow.Block) func(*flow.ExecutionResult) { return func(result *flow.ExecutionResult) { startState := result.Chunks[0].StartState // retain previous start state in case it was user-defined result.BlockID = blockID - result.Chunks = ChunkListFixture(uint(chunks), blockID) - result.Chunks[0].StartState = startState // set start state to value before update + result.Chunks = ChunkListFixture(uint(chunks), blockID, startState) result.PreviousResultID = previousResultID } } func WithChunks(n uint) func(*flow.ExecutionResult) { return func(result *flow.ExecutionResult) { - result.Chunks = ChunkListFixture(n, result.BlockID) + result.Chunks = ChunkListFixture(n, result.BlockID, StateCommitmentFixture()) } } @@ -966,7 +965,7 @@ func ExecutionResultFixture(opts ...func(*flow.ExecutionResult)) *flow.Execution result := &flow.ExecutionResult{ PreviousResultID: IdentifierFixture(), BlockID: executedBlockID, - Chunks: ChunkListFixture(2, executedBlockID), + Chunks: ChunkListFixture(2, executedBlockID, StateCommitmentFixture()), ExecutionDataID: IdentifierFixture(), } @@ -1322,12 +1321,13 @@ func WithChunkStartState(startState flow.StateCommitment) func(chunk *flow.Chunk func ChunkFixture( blockID flow.Identifier, collectionIndex uint, + startState flow.StateCommitment, opts ...func(*flow.Chunk), ) *flow.Chunk { chunk := &flow.Chunk{ ChunkBody: flow.ChunkBody{ CollectionIndex: collectionIndex, - StartState: StateCommitmentFixture(), + StartState: startState, EventCollection: IdentifierFixture(), TotalComputationUsed: 4200, NumberOfTransactions: 42, @@ -1344,12 +1344,13 @@ func ChunkFixture( return chunk } -func ChunkListFixture(n uint, blockID flow.Identifier) flow.ChunkList { +func ChunkListFixture(n uint, blockID flow.Identifier, startState flow.StateCommitment) flow.ChunkList { chunks := make([]*flow.Chunk, 0, n) for i := uint64(0); i < uint64(n); i++ { - chunk := ChunkFixture(blockID, uint(i)) + chunk := ChunkFixture(blockID, uint(i), startState) chunk.Index = i chunks = append(chunks, chunk) + startState = chunk.EndState } return chunks } diff --git a/utils/unittest/logging.go b/utils/unittest/logging.go index c1b993cfab7..507607af822 100644 --- a/utils/unittest/logging.go +++ b/utils/unittest/logging.go @@ -41,7 +41,7 @@ func LoggerWithWriterAndLevel(writer io.Writer, level zerolog.Level) zerolog.Log return log } -// go:noinline +//go:noinline func LoggerForTest(t *testing.T, level zerolog.Level) zerolog.Logger { _, file, _, ok := runtime.Caller(1) if !ok {