diff --git a/cmd/access/node_builder/access_node_builder.go b/cmd/access/node_builder/access_node_builder.go index 4fa5ea49b2c..f4366f44981 100644 --- a/cmd/access/node_builder/access_node_builder.go +++ b/cmd/access/node_builder/access_node_builder.go @@ -59,6 +59,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" @@ -979,7 +980,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 0ca1cc51008..0fa1f45faf3 100644 --- a/cmd/execution_builder.go +++ b/cmd/execution_builder.go @@ -550,7 +550,7 @@ func (exeNode *ExecutionNode) LoadProviderEngine( exeNode.collector, node.Tracer, node.Me, - node.State, + computation.NewProtocolStateWrapper(node.State), vmCtx, ledgerViewCommitter, executionDataProvider, diff --git a/cmd/observer/node_builder/observer_builder.go b/cmd/observer/node_builder/observer_builder.go index f71b7720119..35b5c92b38f 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" @@ -1519,7 +1520,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/engine/access/rpc/backend/script_executor_test.go b/engine/access/rpc/backend/script_executor_test.go index 542d84e417d..2b6afd2bbfd 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/execution/computation/computer/computer.go b/engine/execution/computation/computer/computer.go index c3e77b9be7f..4254d996545 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 { @@ -262,13 +256,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 22215f64642..38d4f20109f 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 1b3765d7b01..9dedb65f005 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/testutil/fixtures.go b/engine/execution/testutil/fixtures.go index 51647bcf9c3..be2c758e43a 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/runtime/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/nodes.go b/engine/testutil/nodes.go index b6d1037b500..588295e02d4 100644 --- a/engine/testutil/nodes.go +++ b/engine/testutil/nodes.go @@ -685,7 +685,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..5c1cf95a425 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) diff --git a/fvm/context.go b/fvm/context.go index fd198633b54..7a579ebc979 100644 --- a/fvm/context.go +++ b/fvm/context.go @@ -14,6 +14,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 ( @@ -170,16 +171,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. @@ -393,11 +384,34 @@ func WithEVMTracer(tracer debug.EVMTracer) 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 cc8af73b239..4f74507116a 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -105,7 +105,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 a616d06e361..940ddbd1b68 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 fc7917f3697..9c8d50402cf 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 2eb12e33826..cd7c9db9339 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 9e6c7d6f519..ac0ec6223f0 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 4cb18e1b471..e621d0ec2b5 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -9,20 +9,6 @@ import ( "math" "strings" "testing" - "time" - - "github.com/rs/zerolog" - - "github.com/coreos/go-semver/semver" - "github.com/onflow/flow-core-contracts/lib/go/templates" - - "github.com/onflow/flow-go/fvm/storage" - "github.com/onflow/flow-go/fvm/storage/state" - - stdlib2 "github.com/onflow/cadence/runtime/stdlib" - - envMock "github.com/onflow/flow-go/fvm/environment/mock" - "github.com/onflow/flow-go/fvm/evm/events" "github.com/onflow/cadence" "github.com/onflow/cadence/encoding/ccf" @@ -32,6 +18,7 @@ import ( cadenceErrors "github.com/onflow/cadence/runtime/errors" "github.com/onflow/cadence/runtime/interpreter" "github.com/onflow/cadence/runtime/sema" + stdlib2 "github.com/onflow/cadence/runtime/stdlib" "github.com/onflow/cadence/runtime/tests/utils" "github.com/onflow/crypto" "github.com/stretchr/testify/assert" @@ -43,7 +30,9 @@ import ( "github.com/onflow/flow-go/fvm" fvmCrypto "github.com/onflow/flow-go/fvm/crypto" "github.com/onflow/flow-go/fvm/environment" + envMock "github.com/onflow/flow-go/fvm/environment/mock" "github.com/onflow/flow-go/fvm/errors" + "github.com/onflow/flow-go/fvm/evm/events" "github.com/onflow/flow-go/fvm/evm/handler" "github.com/onflow/flow-go/fvm/evm/stdlib" "github.com/onflow/flow-go/fvm/evm/types" @@ -51,6 +40,7 @@ import ( reusableRuntime "github.com/onflow/flow-go/fvm/runtime" "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" "github.com/onflow/flow-go/fvm/storage/testutils" "github.com/onflow/flow-go/fvm/systemcontracts" "github.com/onflow/flow-go/fvm/tracing" @@ -3241,196 +3231,6 @@ func TestAccountCapabilitiesPublishEntitledRejection(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 3d302ab35fb..eb7c6ac7a3f 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/runtime/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 25019aacadf..0d0e0a08c6e 100644 --- a/fvm/storage/state/execution_state.go +++ b/fvm/storage/state/execution_state.go @@ -3,8 +3,6 @@ package state import ( "fmt" - "github.com/coreos/go-semver/semver" - "github.com/onflow/cadence/runtime/common" "github.com/onflow/crypto/hash" @@ -29,8 +27,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 @@ -45,7 +42,6 @@ type StateParameters struct { type ExecutionParameters struct { meter.MeterParameters - ExecutionVersion semver.Version } func DefaultParameters() StateParameters { @@ -143,7 +139,6 @@ func (state *ExecutionState) NewChildWithMeterParams( finalized: false, spockState: state.spockState.NewChild(), meter: meter.NewMeter(params.MeterParameters), - executionVersion: params.ExecutionVersion, limitsController: state.limitsController, } } @@ -348,8 +343,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 2bc5aeee402..7fd321d88bc 100644 --- a/fvm/systemcontracts/system_contracts.go +++ b/fvm/systemcontracts/system_contracts.go @@ -64,7 +64,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 582ec6bda1d..2dee806d748 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/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 28653047b45..40975f89ff3 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/runtime/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) @@ -205,7 +197,7 @@ func (s *scriptTestSuite) SetupTest() { logger, metrics.NewNoopCollector(), s.chain.ChainID(), - entropyBlock, + entropyProvider, headers, index.RegisterValue, query.NewDefaultConfig(), 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 +}