From 4312d4d07cde2049021dafcbee947a667b0acaf7 Mon Sep 17 00:00:00 2001 From: Patrick Fuchs Date: Wed, 18 Mar 2026 12:18:47 +0200 Subject: [PATCH 01/10] fvm/evm: cache and defer EVM block proposal persistence to transaction end --- fvm/environment/block_proposal_cache.go | 42 ++++ fvm/environment/env.go | 2 + fvm/environment/facade_env.go | 7 +- fvm/environment/mock/block_proposal_cache.go | 206 ++++++++++++++++++ fvm/environment/mock/environment.go | 170 +++++++++++++++ fvm/evm/backends/wrappedEnv.go | 22 +- fvm/evm/handler/blockstore.go | 54 +++-- fvm/evm/handler/blockstore_benchmark_test.go | 8 +- fvm/evm/handler/blockstore_test.go | 6 +- fvm/evm/handler/handler.go | 15 +- fvm/evm/testutils/backend.go | 13 ++ fvm/evm/types/backend.go | 1 + fvm/evm/types/handler.go | 6 +- .../indexer/extended/mock/index_processor.go | 107 +++++++++ 14 files changed, 620 insertions(+), 39 deletions(-) create mode 100644 fvm/environment/block_proposal_cache.go create mode 100644 fvm/environment/mock/block_proposal_cache.go create mode 100644 module/state_synchronization/indexer/extended/mock/index_processor.go diff --git a/fvm/environment/block_proposal_cache.go b/fvm/environment/block_proposal_cache.go new file mode 100644 index 00000000000..7f3c6883275 --- /dev/null +++ b/fvm/environment/block_proposal_cache.go @@ -0,0 +1,42 @@ +package environment + +// BlockProposalCache provides in-memory caching for the active EVM block proposal, +// scoped to a single Cadence transaction. The value is stored as any to avoid an +// import cycle with the fvm/evm/types package. +type BlockProposalCache interface { + // CachedBlockProposal returns the currently cached block proposal, or nil if none is cached. + CachedBlockProposal() any + // CacheBlockProposal stores the given block proposal in the in-memory cache. + CacheBlockProposal(any) + // SetBlockProposalFlusher registers a function to persist the cached proposal to storage. + // Called by the EVM block store when it first stages a proposal. + SetBlockProposalFlusher(func() error) + // FlushBlockProposal calls the registered flusher to persist the cached proposal. + // It is a no-op if no flusher has been registered. + FlushBlockProposal() error +} + +// blockProposalCache is a simple in-memory cache for a block proposal value. +type blockProposalCache struct { + value any + flusher func() error +} + +func (c *blockProposalCache) CachedBlockProposal() any { + return c.value +} + +func (c *blockProposalCache) CacheBlockProposal(v any) { + c.value = v +} + +func (c *blockProposalCache) SetBlockProposalFlusher(f func() error) { + c.flusher = f +} + +func (c *blockProposalCache) FlushBlockProposal() error { + if c.flusher == nil { + return nil + } + return c.flusher() +} diff --git a/fvm/environment/env.go b/fvm/environment/env.go index 55f2244e2fd..c38edac24bd 100644 --- a/fvm/environment/env.go +++ b/fvm/environment/env.go @@ -85,6 +85,8 @@ type Environment interface { // Reset resets all stateful environment modules (e.g., ContractUpdater, // EventEmitter) to initial state. Reset() + + BlockProposalCache } // ReusableCadenceRuntime is a wrapper around the cadence runtime and environment that diff --git a/fvm/environment/facade_env.go b/fvm/environment/facade_env.go index 7129a2ca453..e69a1175371 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -50,6 +50,7 @@ type facadeEnvironment struct { *ContractReader ContractUpdater + BlockProposalCache *Programs accounts Accounts @@ -146,7 +147,8 @@ func newFacadeEnvironment( accounts, common.Address(sc.Crypto.Address), ), - ContractUpdater: NoContractUpdater{}, + ContractUpdater: NoContractUpdater{}, + BlockProposalCache: &blockProposalCache{}, Programs: NewPrograms( tracer, meter, @@ -331,6 +333,9 @@ func (env *facadeEnvironment) FlushPendingUpdates() ( ContractUpdates, error, ) { + if err := env.BlockProposalCache.FlushBlockProposal(); err != nil { + return ContractUpdates{}, err + } return env.ContractUpdater.Commit() } diff --git a/fvm/environment/mock/block_proposal_cache.go b/fvm/environment/mock/block_proposal_cache.go new file mode 100644 index 00000000000..5d72e8bac44 --- /dev/null +++ b/fvm/environment/mock/block_proposal_cache.go @@ -0,0 +1,206 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mock + +import ( + mock "github.com/stretchr/testify/mock" +) + +// NewBlockProposalCache creates a new instance of BlockProposalCache. 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 NewBlockProposalCache(t interface { + mock.TestingT + Cleanup(func()) +}) *BlockProposalCache { + mock := &BlockProposalCache{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// BlockProposalCache is an autogenerated mock type for the BlockProposalCache type +type BlockProposalCache struct { + mock.Mock +} + +type BlockProposalCache_Expecter struct { + mock *mock.Mock +} + +func (_m *BlockProposalCache) EXPECT() *BlockProposalCache_Expecter { + return &BlockProposalCache_Expecter{mock: &_m.Mock} +} + +// CacheBlockProposal provides a mock function for the type BlockProposalCache +func (_mock *BlockProposalCache) CacheBlockProposal(v any) { + _mock.Called(v) + return +} + +// BlockProposalCache_CacheBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheBlockProposal' +type BlockProposalCache_CacheBlockProposal_Call struct { + *mock.Call +} + +// CacheBlockProposal is a helper method to define mock.On call +// - v any +func (_e *BlockProposalCache_Expecter) CacheBlockProposal(v interface{}) *BlockProposalCache_CacheBlockProposal_Call { + return &BlockProposalCache_CacheBlockProposal_Call{Call: _e.mock.On("CacheBlockProposal", v)} +} + +func (_c *BlockProposalCache_CacheBlockProposal_Call) Run(run func(v any)) *BlockProposalCache_CacheBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 any + if args[0] != nil { + arg0 = args[0].(any) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *BlockProposalCache_CacheBlockProposal_Call) Return() *BlockProposalCache_CacheBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *BlockProposalCache_CacheBlockProposal_Call) RunAndReturn(run func(v any)) *BlockProposalCache_CacheBlockProposal_Call { + _c.Run(run) + return _c +} + +// CachedBlockProposal provides a mock function for the type BlockProposalCache +func (_mock *BlockProposalCache) CachedBlockProposal() any { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for CachedBlockProposal") + } + + var r0 any + if returnFunc, ok := ret.Get(0).(func() any); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(any) + } + } + return r0 +} + +// BlockProposalCache_CachedBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CachedBlockProposal' +type BlockProposalCache_CachedBlockProposal_Call struct { + *mock.Call +} + +// CachedBlockProposal is a helper method to define mock.On call +func (_e *BlockProposalCache_Expecter) CachedBlockProposal() *BlockProposalCache_CachedBlockProposal_Call { + return &BlockProposalCache_CachedBlockProposal_Call{Call: _e.mock.On("CachedBlockProposal")} +} + +func (_c *BlockProposalCache_CachedBlockProposal_Call) Run(run func()) *BlockProposalCache_CachedBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *BlockProposalCache_CachedBlockProposal_Call) Return(v any) *BlockProposalCache_CachedBlockProposal_Call { + _c.Call.Return(v) + return _c +} + +func (_c *BlockProposalCache_CachedBlockProposal_Call) RunAndReturn(run func() any) *BlockProposalCache_CachedBlockProposal_Call { + _c.Call.Return(run) + return _c +} + +// FlushBlockProposal provides a mock function for the type BlockProposalCache +func (_mock *BlockProposalCache) FlushBlockProposal() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for FlushBlockProposal") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// BlockProposalCache_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' +type BlockProposalCache_FlushBlockProposal_Call struct { + *mock.Call +} + +// FlushBlockProposal is a helper method to define mock.On call +func (_e *BlockProposalCache_Expecter) FlushBlockProposal() *BlockProposalCache_FlushBlockProposal_Call { + return &BlockProposalCache_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} +} + +func (_c *BlockProposalCache_FlushBlockProposal_Call) Run(run func()) *BlockProposalCache_FlushBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *BlockProposalCache_FlushBlockProposal_Call) Return(err error) *BlockProposalCache_FlushBlockProposal_Call { + _c.Call.Return(err) + return _c +} + +func (_c *BlockProposalCache_FlushBlockProposal_Call) RunAndReturn(run func() error) *BlockProposalCache_FlushBlockProposal_Call { + _c.Call.Return(run) + return _c +} + +// SetBlockProposalFlusher provides a mock function for the type BlockProposalCache +func (_mock *BlockProposalCache) SetBlockProposalFlusher(fn func() error) { + _mock.Called(fn) + return +} + +// BlockProposalCache_SetBlockProposalFlusher_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetBlockProposalFlusher' +type BlockProposalCache_SetBlockProposalFlusher_Call struct { + *mock.Call +} + +// SetBlockProposalFlusher is a helper method to define mock.On call +// - fn func() error +func (_e *BlockProposalCache_Expecter) SetBlockProposalFlusher(fn interface{}) *BlockProposalCache_SetBlockProposalFlusher_Call { + return &BlockProposalCache_SetBlockProposalFlusher_Call{Call: _e.mock.On("SetBlockProposalFlusher", fn)} +} + +func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) Run(run func(fn func() error)) *BlockProposalCache_SetBlockProposalFlusher_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 func() error + if args[0] != nil { + arg0 = args[0].(func() error) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) Return() *BlockProposalCache_SetBlockProposalFlusher_Call { + _c.Call.Return() + return _c +} + +func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) RunAndReturn(run func(fn func() error)) *BlockProposalCache_SetBlockProposalFlusher_Call { + _c.Run(run) + return _c +} diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index ff7c29bbbc6..b1b748394bd 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -564,6 +564,92 @@ func (_c *Environment_BorrowCadenceRuntime_Call) RunAndReturn(run func() environ return _c } +// CacheBlockProposal provides a mock function for the type Environment +func (_mock *Environment) CacheBlockProposal(v any) { + _mock.Called(v) + return +} + +// Environment_CacheBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheBlockProposal' +type Environment_CacheBlockProposal_Call struct { + *mock.Call +} + +// CacheBlockProposal is a helper method to define mock.On call +// - v any +func (_e *Environment_Expecter) CacheBlockProposal(v interface{}) *Environment_CacheBlockProposal_Call { + return &Environment_CacheBlockProposal_Call{Call: _e.mock.On("CacheBlockProposal", v)} +} + +func (_c *Environment_CacheBlockProposal_Call) Run(run func(v any)) *Environment_CacheBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 any + if args[0] != nil { + arg0 = args[0].(any) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *Environment_CacheBlockProposal_Call) Return() *Environment_CacheBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *Environment_CacheBlockProposal_Call) RunAndReturn(run func(v any)) *Environment_CacheBlockProposal_Call { + _c.Run(run) + return _c +} + +// CachedBlockProposal provides a mock function for the type Environment +func (_mock *Environment) CachedBlockProposal() any { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for CachedBlockProposal") + } + + var r0 any + if returnFunc, ok := ret.Get(0).(func() any); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(any) + } + } + return r0 +} + +// Environment_CachedBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CachedBlockProposal' +type Environment_CachedBlockProposal_Call struct { + *mock.Call +} + +// CachedBlockProposal is a helper method to define mock.On call +func (_e *Environment_Expecter) CachedBlockProposal() *Environment_CachedBlockProposal_Call { + return &Environment_CachedBlockProposal_Call{Call: _e.mock.On("CachedBlockProposal")} +} + +func (_c *Environment_CachedBlockProposal_Call) Run(run func()) *Environment_CachedBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Environment_CachedBlockProposal_Call) Return(v any) *Environment_CachedBlockProposal_Call { + _c.Call.Return(v) + return _c +} + +func (_c *Environment_CachedBlockProposal_Call) RunAndReturn(run func() any) *Environment_CachedBlockProposal_Call { + _c.Call.Return(run) + return _c +} + // CheckPayerBalanceAndGetMaxTxFees provides a mock function for the type Environment func (_mock *Environment) CheckPayerBalanceAndGetMaxTxFees(payer flow.Address, inclusionEffort uint64, executionEffort uint64) (cadence.Value, error) { ret := _mock.Called(payer, inclusionEffort, executionEffort) @@ -1334,6 +1420,50 @@ func (_c *Environment_Events_Call) RunAndReturn(run func() flow.EventsList) *Env return _c } +// FlushBlockProposal provides a mock function for the type Environment +func (_mock *Environment) FlushBlockProposal() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for FlushBlockProposal") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Environment_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' +type Environment_FlushBlockProposal_Call struct { + *mock.Call +} + +// FlushBlockProposal is a helper method to define mock.On call +func (_e *Environment_Expecter) FlushBlockProposal() *Environment_FlushBlockProposal_Call { + return &Environment_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} +} + +func (_c *Environment_FlushBlockProposal_Call) Run(run func()) *Environment_FlushBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Environment_FlushBlockProposal_Call) Return(err error) *Environment_FlushBlockProposal_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Environment_FlushBlockProposal_Call) RunAndReturn(run func() error) *Environment_FlushBlockProposal_Call { + _c.Call.Return(run) + return _c +} + // FlushPendingUpdates provides a mock function for the type Environment func (_mock *Environment) FlushPendingUpdates() (environment.ContractUpdates, error) { ret := _mock.Called() @@ -3965,6 +4095,46 @@ func (_c *Environment_ServiceEvents_Call) RunAndReturn(run func() flow.EventsLis return _c } +// SetBlockProposalFlusher provides a mock function for the type Environment +func (_mock *Environment) SetBlockProposalFlusher(fn func() error) { + _mock.Called(fn) + return +} + +// Environment_SetBlockProposalFlusher_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetBlockProposalFlusher' +type Environment_SetBlockProposalFlusher_Call struct { + *mock.Call +} + +// SetBlockProposalFlusher is a helper method to define mock.On call +// - fn func() error +func (_e *Environment_Expecter) SetBlockProposalFlusher(fn interface{}) *Environment_SetBlockProposalFlusher_Call { + return &Environment_SetBlockProposalFlusher_Call{Call: _e.mock.On("SetBlockProposalFlusher", fn)} +} + +func (_c *Environment_SetBlockProposalFlusher_Call) Run(run func(fn func() error)) *Environment_SetBlockProposalFlusher_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 func() error + if args[0] != nil { + arg0 = args[0].(func() error) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *Environment_SetBlockProposalFlusher_Call) Return() *Environment_SetBlockProposalFlusher_Call { + _c.Call.Return() + return _c +} + +func (_c *Environment_SetBlockProposalFlusher_Call) RunAndReturn(run func(fn func() error)) *Environment_SetBlockProposalFlusher_Call { + _c.Run(run) + return _c +} + // SetNumberOfDeployedCOAs provides a mock function for the type Environment func (_mock *Environment) SetNumberOfDeployedCOAs(count uint64) { _mock.Called(count) diff --git a/fvm/evm/backends/wrappedEnv.go b/fvm/evm/backends/wrappedEnv.go index d63ed8b2f17..5bf4567446a 100644 --- a/fvm/evm/backends/wrappedEnv.go +++ b/fvm/evm/backends/wrappedEnv.go @@ -25,7 +25,7 @@ type WrappedEnvironment struct { // NewWrappedEnvironment constructs a new wrapped environment func NewWrappedEnvironment(env environment.Environment) *WrappedEnvironment { - return &WrappedEnvironment{env} + return &WrappedEnvironment{env: env} } var _ types.Backend = &WrappedEnvironment{} @@ -137,6 +137,26 @@ func (we *WrappedEnvironment) Reset() { we.env.Reset() } +// CachedBlockProposal returns the currently cached block proposal, or nil if none is cached. +func (we *WrappedEnvironment) CachedBlockProposal() any { + return we.env.CachedBlockProposal() +} + +// CacheBlockProposal stores the given block proposal in the in-memory cache. +func (we *WrappedEnvironment) CacheBlockProposal(v any) { + we.env.CacheBlockProposal(v) +} + +// SetBlockProposalFlusher registers a function to persist the cached proposal to storage. +func (we *WrappedEnvironment) SetBlockProposalFlusher(f func() error) { + we.env.SetBlockProposalFlusher(f) +} + +// FlushBlockProposal calls the registered flusher to persist the cached proposal. +func (we *WrappedEnvironment) FlushBlockProposal() error { + return we.env.FlushBlockProposal() +} + // GetCurrentBlockHeight returns the current Flow block height func (we *WrappedEnvironment) GetCurrentBlockHeight() (uint64, error) { val, err := we.env.GetCurrentBlockHeight() diff --git a/fvm/evm/handler/blockstore.go b/fvm/evm/handler/blockstore.go index 3e1f2581c02..6de2ea36012 100644 --- a/fvm/evm/handler/blockstore.go +++ b/fvm/evm/handler/blockstore.go @@ -16,6 +16,13 @@ const ( BlockStoreLatestBlockProposalKey = "LatestBlockProposal" ) +// BlockStore manages EVM block proposal and block hash data for a single Cadence transaction. +// The block proposal is cached in memory (via the backend's [types.BlockProposalCache]) to avoid +// repeated serialization/deserialization overhead across multiple reads and writes within the same +// Cadence transaction execution. +// Writes to storage are deferred: [StageBlockProposal] only updates the in-memory cache, +// and persistence is handled automatically at transaction end via [BlockProposalCache.FlushBlockProposal]. +// CAUTION: not concurrency safe. type BlockStore struct { chainID flow.ChainID backend types.Backend @@ -30,32 +37,38 @@ func NewBlockStore( backend types.Backend, rootAddress flow.Address, ) *BlockStore { - return &BlockStore{ + bs := &BlockStore{ chainID: chainID, backend: backend, rootAddress: rootAddress, } + return bs } // BlockProposal returns the block proposal to be updated by the handler func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { - // first fetch it from the storage + cached, ok := bs.backend.CachedBlockProposal().(*types.BlockProposal) + if ok && cached != nil { + return cached, nil + } + // fetch from storage data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey)) if err != nil { return nil, err } if len(data) != 0 { - return types.NewBlockProposalFromBytes(data) + bp, err := types.NewBlockProposalFromBytes(data) + if err != nil { + return nil, err + } + bs.backend.CacheBlockProposal(bp) + return bp, nil } bp, err := bs.constructBlockProposal() if err != nil { return nil, err } - // store block proposal - err = bs.UpdateBlockProposal(bp) - if err != nil { - return nil, err - } + bs.StageBlockProposal(bp) return bp, nil } @@ -106,13 +119,25 @@ func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { return blockProposal, nil } -// UpdateBlockProposal updates the block proposal -func (bs *BlockStore) UpdateBlockProposal(bp *types.BlockProposal) error { - blockProposalBytes, err := bp.ToBytes() +// StageBlockProposal updates the in-memory block proposal cache and registers the +// persistence flusher on the backend so it is called at the end of the Cadence transaction. +func (bs *BlockStore) StageBlockProposal(bp *types.BlockProposal) { + bs.backend.SetBlockProposalFlusher(bs.FlushBlockProposal) + bs.backend.CacheBlockProposal(bp) +} + +// flushBlockProposal writes the cached block proposal to storage. +// It is registered on the backend's BlockProposalCache and called by +// facadeEnvironment.FlushPendingUpdates at the end of the Cadence transaction. +func (bs *BlockStore) FlushBlockProposal() error { + cached, ok := bs.backend.CachedBlockProposal().(*types.BlockProposal) + if !ok || cached == nil { + return nil + } + blockProposalBytes, err := cached.ToBytes() if err != nil { return types.NewFatalError(err) } - return bs.backend.SetValue( bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey), @@ -153,10 +178,7 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { if err != nil { return err } - err = bs.UpdateBlockProposal(newBP) - if err != nil { - return err - } + bs.StageBlockProposal(newBP) return nil } diff --git a/fvm/evm/handler/blockstore_benchmark_test.go b/fvm/evm/handler/blockstore_benchmark_test.go index 013b6326b0a..ffca5cdb4c8 100644 --- a/fvm/evm/handler/blockstore_benchmark_test.go +++ b/fvm/evm/handler/blockstore_benchmark_test.go @@ -23,9 +23,10 @@ func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { require.NoError(b, err) res := testutils.RandomResultFixture(b) bp.AppendTransaction(res) - err = bs.UpdateBlockProposal(bp) - require.NoError(b, err) + bs.StageBlockProposal(bp) } + err := bs.FlushBlockProposal() + require.NoError(b, err) // check the impact of updating block proposal after x number of transactions backend.ResetStats() @@ -34,7 +35,8 @@ func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { require.NoError(b, err) res := testutils.RandomResultFixture(b) bp.AppendTransaction(res) - err = bs.UpdateBlockProposal(bp) + bs.StageBlockProposal(bp) + err = bs.FlushBlockProposal() require.NoError(b, err) b.ReportMetric(float64(time.Since(startTime).Nanoseconds()), "proposal_update_time_ns") diff --git a/fvm/evm/handler/blockstore_test.go b/fvm/evm/handler/blockstore_test.go index c2e40135949..ff0722020a7 100644 --- a/fvm/evm/handler/blockstore_test.go +++ b/fvm/evm/handler/blockstore_test.go @@ -43,8 +43,7 @@ func TestBlockStore(t *testing.T) { // update the block proposal bp.TotalGasUsed += 100 - err = bs.UpdateBlockProposal(bp) - require.NoError(t, err) + bs.StageBlockProposal(bp) // reset the bs and check if it still return the block proposal bs = handler.NewBlockStore(chainID, backend, root) @@ -55,8 +54,7 @@ func TestBlockStore(t *testing.T) { // update the block proposal again supply := big.NewInt(100) bp.TotalSupply = supply - err = bs.UpdateBlockProposal(bp) - require.NoError(t, err) + bs.StageBlockProposal(bp) // this should still return the genesis block retb, err := bs.LatestBlock() require.NoError(t, err) diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 371686ae2fc..335c4a7b56f 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -372,10 +372,7 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte) ([]*types.Result, err } // update the block proposal - err = h.blockStore.UpdateBlockProposal(bp) - if err != nil { - return nil, err - } + h.blockStore.StageBlockProposal(bp) return res, nil } @@ -481,10 +478,7 @@ func (h *ContractHandler) run(rlpEncodedTx []byte) (*types.Result, error) { // step 8 - update the block proposal bp.AppendTransaction(res) - err = h.blockStore.UpdateBlockProposal(bp) - if err != nil { - return nil, err - } + h.blockStore.StageBlockProposal(bp) // step 9 - emit transaction event err = h.emitEvent( @@ -753,10 +747,7 @@ func (h *ContractHandler) executeAndHandleCall( } // update the block proposal - err = h.blockStore.UpdateBlockProposal(bp) - if err != nil { - return nil, err - } + h.blockStore.StageBlockProposal(bp) // step 8 - emit transaction event encoded, err := call.Encode() diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 31ac2349ce5..ffdda66084c 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -239,12 +239,25 @@ type TestBackend struct { *TestMetricsReporter *TestLoggerProvider evmTestOperationsAllowed bool + cachedProposal any } func (tb *TestBackend) EVMTestOperationsAllowed() bool { return tb.evmTestOperationsAllowed } +func (tb *TestBackend) CachedBlockProposal() any { + return tb.cachedProposal +} + +func (tb *TestBackend) CacheBlockProposal(v any) { + tb.cachedProposal = v +} + +func (tb *TestBackend) SetBlockProposalFlusher(func() error) {} + +func (tb *TestBackend) FlushBlockProposal() error { return nil } + var _ types.Backend = &TestBackend{} func (tb *TestBackend) TotalStorageSize() int { diff --git a/fvm/evm/types/backend.go b/fvm/evm/types/backend.go index 0a60d109324..ec0e7d188eb 100644 --- a/fvm/evm/types/backend.go +++ b/fvm/evm/types/backend.go @@ -14,6 +14,7 @@ type BackendStorage interface { // a `BackendError`. type Backend interface { BackendStorage + environment.BlockProposalCache environment.Meter environment.EventEmitter environment.BlockInfo diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index 144d44f12a7..62b1c06b5d5 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -102,8 +102,10 @@ type BlockStore interface { // BlockProposal returns the active block proposal BlockProposal() (*BlockProposal, error) - // UpdateBlockProposal replaces the current block proposal with the ones passed - UpdateBlockProposal(*BlockProposal) error + // StageBlockProposal updates the in-memory block proposal cache without writing to storage. + // Persistence is handled automatically at the end of the Cadence transaction via + // the flusher registered on the backend's BlockProposalCache. + StageBlockProposal(*BlockProposal) // CommitBlockProposal commits the block proposal and update the chain of blocks CommitBlockProposal(*BlockProposal) error diff --git a/module/state_synchronization/indexer/extended/mock/index_processor.go b/module/state_synchronization/indexer/extended/mock/index_processor.go new file mode 100644 index 00000000000..753d52467d0 --- /dev/null +++ b/module/state_synchronization/indexer/extended/mock/index_processor.go @@ -0,0 +1,107 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mock + +import ( + "github.com/onflow/flow-go/module/state_synchronization/indexer/extended" + mock "github.com/stretchr/testify/mock" +) + +// NewIndexProcessor creates a new instance of IndexProcessor. 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 NewIndexProcessor[T any, M any](t interface { + mock.TestingT + Cleanup(func()) +}) *IndexProcessor[T, M] { + mock := &IndexProcessor[T, M]{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// IndexProcessor is an autogenerated mock type for the IndexProcessor type +type IndexProcessor[T any, M any] struct { + mock.Mock +} + +type IndexProcessor_Expecter[T any, M any] struct { + mock *mock.Mock +} + +func (_m *IndexProcessor[T, M]) EXPECT() *IndexProcessor_Expecter[T, M] { + return &IndexProcessor_Expecter[T, M]{mock: &_m.Mock} +} + +// ProcessBlockData provides a mock function for the type IndexProcessor +func (_mock *IndexProcessor[T, M]) ProcessBlockData(data extended.BlockData) ([]T, M, error) { + ret := _mock.Called(data) + + if len(ret) == 0 { + panic("no return value specified for ProcessBlockData") + } + + var r0 []T + var r1 M + var r2 error + if returnFunc, ok := ret.Get(0).(func(extended.BlockData) ([]T, M, error)); ok { + return returnFunc(data) + } + if returnFunc, ok := ret.Get(0).(func(extended.BlockData) []T); ok { + r0 = returnFunc(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]T) + } + } + if returnFunc, ok := ret.Get(1).(func(extended.BlockData) M); ok { + r1 = returnFunc(data) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(M) + } + } + if returnFunc, ok := ret.Get(2).(func(extended.BlockData) error); ok { + r2 = returnFunc(data) + } else { + r2 = ret.Error(2) + } + return r0, r1, r2 +} + +// IndexProcessor_ProcessBlockData_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ProcessBlockData' +type IndexProcessor_ProcessBlockData_Call[T any, M any] struct { + *mock.Call +} + +// ProcessBlockData is a helper method to define mock.On call +// - data extended.BlockData +func (_e *IndexProcessor_Expecter[T, M]) ProcessBlockData(data interface{}) *IndexProcessor_ProcessBlockData_Call[T, M] { + return &IndexProcessor_ProcessBlockData_Call[T, M]{Call: _e.mock.On("ProcessBlockData", data)} +} + +func (_c *IndexProcessor_ProcessBlockData_Call[T, M]) Run(run func(data extended.BlockData)) *IndexProcessor_ProcessBlockData_Call[T, M] { + _c.Call.Run(func(args mock.Arguments) { + var arg0 extended.BlockData + if args[0] != nil { + arg0 = args[0].(extended.BlockData) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *IndexProcessor_ProcessBlockData_Call[T, M]) Return(vs []T, v M, err error) *IndexProcessor_ProcessBlockData_Call[T, M] { + _c.Call.Return(vs, v, err) + return _c +} + +func (_c *IndexProcessor_ProcessBlockData_Call[T, M]) RunAndReturn(run func(data extended.BlockData) ([]T, M, error)) *IndexProcessor_ProcessBlockData_Call[T, M] { + _c.Call.Return(run) + return _c +} From 5b7f8416ebdee95a1d81ba9d7fa6be108dbc5021 Mon Sep 17 00:00:00 2001 From: Patrick Fuchs Date: Thu, 2 Apr 2026 00:01:45 +0200 Subject: [PATCH 02/10] fvm/evm: move BlockStore to fvm environment as EVMBlockStore --- cmd/util/cmd/checkpoint-collect-stats/cmd.go | 6 +- fvm/environment/block_proposal_cache.go | 42 -- fvm/environment/env.go | 2 +- .../evm_block_hash_list.go} | 7 +- .../evm_block_hash_list_test.go} | 8 +- .../evm_block_store.go} | 155 +++-- .../evm_block_store_benchmark_test.go} | 11 +- .../evm_block_store_test.go} | 13 +- fvm/environment/facade_env.go | 62 +- fvm/environment/mock/block_proposal_cache.go | 206 ------- fvm/environment/mock/environment.go | 558 ++++++++++++------ fvm/environment/mock/evm_block_store.go | 378 ++++++++++++ .../reusable_cadence_runtime_interface.go | 190 ------ fvm/evm/{types => backends}/backend.go | 4 +- fvm/evm/backends/wrappedEnv.go | 65 +- fvm/evm/evm_test.go | 22 +- fvm/evm/handler/handler.go | 24 +- fvm/evm/handler/handler_test.go | 65 +- fvm/evm/handler/precompiles.go | 11 +- fvm/evm/offchain/blocks/blocks.go | 13 +- fvm/evm/offchain/blocks/provider.go | 11 +- fvm/evm/offchain/storage/ephemeral.go | 7 +- fvm/evm/offchain/storage/readonly.go | 3 +- fvm/evm/offchain/sync/replay.go | 3 +- fvm/evm/testutils/backend.go | 125 +++- fvm/evm/testutils/handler.go | 4 +- fvm/evm/testutils/offchain.go | 3 +- fvm/evm/types/handler.go | 20 - fvm/fvm_test.go | 7 +- fvm/runtime/cadence_function_declarations.go | 2 - 30 files changed, 1162 insertions(+), 865 deletions(-) delete mode 100644 fvm/environment/block_proposal_cache.go rename fvm/{evm/handler/blockHashList.go => environment/evm_block_hash_list.go} (98%) rename fvm/{evm/handler/blockHashList_test.go => environment/evm_block_hash_list_test.go} (93%) rename fvm/{evm/handler/blockstore.go => environment/evm_block_store.go} (55%) rename fvm/{evm/handler/blockstore_benchmark_test.go => environment/evm_block_store_benchmark_test.go} (88%) rename fvm/{evm/handler/blockstore_test.go => environment/evm_block_store_test.go} (87%) delete mode 100644 fvm/environment/mock/block_proposal_cache.go create mode 100644 fvm/environment/mock/evm_block_store.go delete mode 100644 fvm/environment/mock/reusable_cadence_runtime_interface.go rename fvm/evm/{types => backends}/backend.go (93%) diff --git a/cmd/util/cmd/checkpoint-collect-stats/cmd.go b/cmd/util/cmd/checkpoint-collect-stats/cmd.go index 2e53fe9528a..3269f4914cf 100644 --- a/cmd/util/cmd/checkpoint-collect-stats/cmd.go +++ b/cmd/util/cmd/checkpoint-collect-stats/cmd.go @@ -19,8 +19,8 @@ import ( "github.com/onflow/flow-go/cmd/util/ledger/reporters" "github.com/onflow/flow-go/cmd/util/ledger/util" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/emulator/state" - "github.com/onflow/flow-go/fvm/evm/handler" "github.com/onflow/flow-go/ledger" "github.com/onflow/flow-go/ledger/common/pathfinder" "github.com/onflow/flow-go/ledger/complete" @@ -465,9 +465,9 @@ func getRegisterType(key ledger.Key) string { return "account storage ID" case state.CodesStorageIDKey: return "code storage ID" - case handler.BlockStoreLatestBlockKey: + case environment.BlockStoreLatestBlockKey: return "latest block" - case handler.BlockStoreLatestBlockProposalKey: + case environment.BlockStoreLatestBlockProposalKey: return "latest block proposal" } diff --git a/fvm/environment/block_proposal_cache.go b/fvm/environment/block_proposal_cache.go deleted file mode 100644 index 7f3c6883275..00000000000 --- a/fvm/environment/block_proposal_cache.go +++ /dev/null @@ -1,42 +0,0 @@ -package environment - -// BlockProposalCache provides in-memory caching for the active EVM block proposal, -// scoped to a single Cadence transaction. The value is stored as any to avoid an -// import cycle with the fvm/evm/types package. -type BlockProposalCache interface { - // CachedBlockProposal returns the currently cached block proposal, or nil if none is cached. - CachedBlockProposal() any - // CacheBlockProposal stores the given block proposal in the in-memory cache. - CacheBlockProposal(any) - // SetBlockProposalFlusher registers a function to persist the cached proposal to storage. - // Called by the EVM block store when it first stages a proposal. - SetBlockProposalFlusher(func() error) - // FlushBlockProposal calls the registered flusher to persist the cached proposal. - // It is a no-op if no flusher has been registered. - FlushBlockProposal() error -} - -// blockProposalCache is a simple in-memory cache for a block proposal value. -type blockProposalCache struct { - value any - flusher func() error -} - -func (c *blockProposalCache) CachedBlockProposal() any { - return c.value -} - -func (c *blockProposalCache) CacheBlockProposal(v any) { - c.value = v -} - -func (c *blockProposalCache) SetBlockProposalFlusher(f func() error) { - c.flusher = f -} - -func (c *blockProposalCache) FlushBlockProposal() error { - if c.flusher == nil { - return nil - } - return c.flusher() -} diff --git a/fvm/environment/env.go b/fvm/environment/env.go index c38edac24bd..53727315844 100644 --- a/fvm/environment/env.go +++ b/fvm/environment/env.go @@ -86,7 +86,7 @@ type Environment interface { // EventEmitter) to initial state. Reset() - BlockProposalCache + EVMBlockStore } // ReusableCadenceRuntime is a wrapper around the cadence runtime and environment that diff --git a/fvm/evm/handler/blockHashList.go b/fvm/environment/evm_block_hash_list.go similarity index 98% rename from fvm/evm/handler/blockHashList.go rename to fvm/environment/evm_block_hash_list.go index 635e1b3fc82..cd85d2f4093 100644 --- a/fvm/evm/handler/blockHashList.go +++ b/fvm/environment/evm_block_hash_list.go @@ -1,4 +1,4 @@ -package handler +package environment import ( "encoding/binary" @@ -7,7 +7,6 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" - "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -41,7 +40,7 @@ func IsBlockHashListMetaKey(id flow.RegisterID) bool { // smaller fixed size buckets to minimize the // number of bytes read and written during set/get operations. type BlockHashList struct { - backend types.BackendStorage + backend ValueStore rootAddress flow.Address // cached meta data @@ -55,7 +54,7 @@ type BlockHashList struct { // It tries to load the metadata from the backend // and if not exist it creates one func NewBlockHashList( - backend types.BackendStorage, + backend ValueStore, rootAddress flow.Address, capacity int, ) (*BlockHashList, error) { diff --git a/fvm/evm/handler/blockHashList_test.go b/fvm/environment/evm_block_hash_list_test.go similarity index 93% rename from fvm/evm/handler/blockHashList_test.go rename to fvm/environment/evm_block_hash_list_test.go index ebf1b21e1c8..869594018e5 100644 --- a/fvm/evm/handler/blockHashList_test.go +++ b/fvm/environment/evm_block_hash_list_test.go @@ -1,4 +1,4 @@ -package handler_test +package environment_test import ( "testing" @@ -6,7 +6,7 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/model/flow" ) @@ -15,7 +15,7 @@ func TestBlockHashList(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { capacity := 256 - bhl, err := handler.NewBlockHashList(backend, root, capacity) + bhl, err := environment.NewBlockHashList(backend, root, capacity) require.NoError(t, err) require.True(t, bhl.IsEmpty()) @@ -75,7 +75,7 @@ func TestBlockHashList(t *testing.T) { require.Equal(t, gethCommon.Hash{byte(capacity + 2)}, h) // construct a new one and check - bhl, err = handler.NewBlockHashList(backend, root, capacity) + bhl, err = environment.NewBlockHashList(backend, root, capacity) require.NoError(t, err) require.False(t, bhl.IsEmpty()) diff --git a/fvm/evm/handler/blockstore.go b/fvm/environment/evm_block_store.go similarity index 55% rename from fvm/evm/handler/blockstore.go rename to fvm/environment/evm_block_store.go index 6de2ea36012..d6b010e6580 100644 --- a/fvm/evm/handler/blockstore.go +++ b/fvm/environment/evm_block_store.go @@ -1,4 +1,4 @@ -package handler +package environment import ( "fmt" @@ -10,49 +10,74 @@ import ( "github.com/onflow/flow-go/model/flow" ) +// BlockStore stores the chain of blocks +type EVMBlockStore interface { + // LatestBlock returns the latest appended block + LatestBlock() (*types.Block, error) + + // BlockHash returns the hash of the block at the given height + BlockHash(height uint64) (gethCommon.Hash, error) + + // BlockProposal returns the active block proposal + BlockProposal() (*types.BlockProposal, error) + + // StageBlockProposal updates the in-memory block proposal without writing to + // storage. Persistence happens at transaction end via FlushBlockProposal. + StageBlockProposal(*types.BlockProposal) + + // FlushBlockProposal writes the cached block proposal to storage. Called by the + // fvm.Environment at the end of each Cadence transaction. + FlushBlockProposal() error + + // CommitBlockProposal commits the block proposal and update the chain of blocks + CommitBlockProposal(*types.BlockProposal) error + + // Reset discards any staged but unflushed block proposal. Called by the + // fvm.Environment when a transaction fails. + ResetBlockProposal() +} + const ( BlockHashListCapacity = 256 BlockStoreLatestBlockKey = "LatestBlock" BlockStoreLatestBlockProposalKey = "LatestBlockProposal" ) -// BlockStore manages EVM block proposal and block hash data for a single Cadence transaction. -// The block proposal is cached in memory (via the backend's [types.BlockProposalCache]) to avoid -// repeated serialization/deserialization overhead across multiple reads and writes within the same -// Cadence transaction execution. -// Writes to storage are deferred: [StageBlockProposal] only updates the in-memory cache, -// and persistence is handled automatically at transaction end via [BlockProposalCache.FlushBlockProposal]. -// CAUTION: not concurrency safe. type BlockStore struct { chainID flow.ChainID - backend types.Backend + storage ValueStore + blockInfo BlockInfo + randGen RandomGenerator rootAddress flow.Address + cached *types.BlockProposal } -var _ types.BlockStore = &BlockStore{} +var _ EVMBlockStore = &BlockStore{} // NewBlockStore constructs a new block store func NewBlockStore( chainID flow.ChainID, - backend types.Backend, + storage ValueStore, + blockInfo BlockInfo, + randGen RandomGenerator, rootAddress flow.Address, ) *BlockStore { - bs := &BlockStore{ + return &BlockStore{ chainID: chainID, - backend: backend, + storage: storage, + blockInfo: blockInfo, + randGen: randGen, rootAddress: rootAddress, } - return bs } // BlockProposal returns the block proposal to be updated by the handler func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { - cached, ok := bs.backend.CachedBlockProposal().(*types.BlockProposal) - if ok && cached != nil { - return cached, nil + if bs.cached != nil { + return bs.cached, nil } - // fetch from storage - data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey)) + // first fetch it from the storage + data, err := bs.storage.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey)) if err != nil { return nil, err } @@ -61,25 +86,25 @@ func (bs *BlockStore) BlockProposal() (*types.BlockProposal, error) { if err != nil { return nil, err } - bs.backend.CacheBlockProposal(bp) + bs.cached = bp return bp, nil } bp, err := bs.constructBlockProposal() if err != nil { return nil, err } - bs.StageBlockProposal(bp) + bs.cached = bp return bp, nil } func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { // if available construct a new one - cadenceHeight, err := bs.backend.GetCurrentBlockHeight() + cadenceHeight, err := bs.blockInfo.GetCurrentBlockHeight() if err != nil { return nil, err } - cadenceBlock, found, err := bs.backend.GetBlockAtHeight(cadenceHeight) + cadenceBlock, found, err := bs.blockInfo.GetBlockAtHeight(cadenceHeight) if err != nil { return nil, err } @@ -103,7 +128,7 @@ func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { // read a random value for block proposal prevrandao := gethCommon.Hash{} - err = bs.backend.ReadRandom(prevrandao[:]) + err = bs.randGen.ReadRandom(prevrandao[:]) if err != nil { return nil, err } @@ -119,26 +144,14 @@ func (bs *BlockStore) constructBlockProposal() (*types.BlockProposal, error) { return blockProposal, nil } -// StageBlockProposal updates the in-memory block proposal cache and registers the -// persistence flusher on the backend so it is called at the end of the Cadence transaction. -func (bs *BlockStore) StageBlockProposal(bp *types.BlockProposal) { - bs.backend.SetBlockProposalFlusher(bs.FlushBlockProposal) - bs.backend.CacheBlockProposal(bp) -} - -// flushBlockProposal writes the cached block proposal to storage. -// It is registered on the backend's BlockProposalCache and called by -// facadeEnvironment.FlushPendingUpdates at the end of the Cadence transaction. -func (bs *BlockStore) FlushBlockProposal() error { - cached, ok := bs.backend.CachedBlockProposal().(*types.BlockProposal) - if !ok || cached == nil { - return nil - } - blockProposalBytes, err := cached.ToBytes() +// UpdateBlockProposal updates the block proposal +func (bs *BlockStore) updateBlockProposal(bp *types.BlockProposal) error { + blockProposalBytes, err := bp.ToBytes() if err != nil { return types.NewFatalError(err) } - return bs.backend.SetValue( + + return bs.storage.SetValue( bs.rootAddress[:], []byte(BlockStoreLatestBlockProposalKey), blockProposalBytes, @@ -154,7 +167,7 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { return types.NewFatalError(err) } - err = bs.backend.SetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey), blockBytes) + err = bs.storage.SetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey), blockBytes) if err != nil { return err } @@ -178,14 +191,17 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { if err != nil { return err } - bs.StageBlockProposal(newBP) - + err = bs.updateBlockProposal(newBP) + if err != nil { + return err + } + bs.cached = newBP return nil } // LatestBlock returns the latest executed block func (bs *BlockStore) LatestBlock() (*types.Block, error) { - data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey)) + data, err := bs.storage.GetValue(bs.rootAddress[:], []byte(BlockStoreLatestBlockKey)) if err != nil { return nil, err } @@ -206,7 +222,7 @@ func (bs *BlockStore) BlockHash(height uint64) (gethCommon.Hash, error) { } func (bs *BlockStore) getBlockHashList() (*BlockHashList, error) { - bhl, err := NewBlockHashList(bs.backend, bs.rootAddress, BlockHashListCapacity) + bhl, err := NewBlockHashList(bs.storage, bs.rootAddress, BlockHashListCapacity) if err != nil { return nil, err } @@ -223,3 +239,50 @@ func (bs *BlockStore) getBlockHashList() (*BlockHashList, error) { return bhl, nil } + +func (bs *BlockStore) ResetBlockProposal() { + bs.cached = nil +} + +func (bs *BlockStore) StageBlockProposal(bp *types.BlockProposal) { + bs.cached = bp +} + +func (bs *BlockStore) FlushBlockProposal() error { + if bs.cached == nil { + return nil + } + err := bs.updateBlockProposal(bs.cached) + if err != nil { + return err + } + return nil +} + +type NoEVMBlockStore struct{} + +var _ EVMBlockStore = &NoEVMBlockStore{} + +func (bs NoEVMBlockStore) BlockProposal() (*types.BlockProposal, error) { + return nil, nil +} + +func (bs NoEVMBlockStore) CommitBlockProposal(bp *types.BlockProposal) error { + return nil +} + +func (bs NoEVMBlockStore) LatestBlock() (*types.Block, error) { + return nil, nil +} + +func (bs NoEVMBlockStore) BlockHash(height uint64) (gethCommon.Hash, error) { + return gethCommon.Hash{}, nil +} + +func (bs NoEVMBlockStore) ResetBlockProposal() {} + +func (bs NoEVMBlockStore) StageBlockProposal(bp *types.BlockProposal) {} + +func (bs NoEVMBlockStore) FlushBlockProposal() error { + return nil +} diff --git a/fvm/evm/handler/blockstore_benchmark_test.go b/fvm/environment/evm_block_store_benchmark_test.go similarity index 88% rename from fvm/evm/handler/blockstore_benchmark_test.go rename to fvm/environment/evm_block_store_benchmark_test.go index ffca5cdb4c8..28c6ecfc55c 100644 --- a/fvm/evm/handler/blockstore_benchmark_test.go +++ b/fvm/environment/evm_block_store_benchmark_test.go @@ -1,4 +1,4 @@ -package handler_test +package environment_test import ( "testing" @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/model/flow" ) @@ -17,16 +17,15 @@ func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { testutils.RunWithTestBackend(b, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(b, backend, func(rootAddr flow.Address) { - bs := handler.NewBlockStore(flow.Testnet, backend, rootAddr) + bs := environment.NewBlockStore(flow.Testnet, backend, backend, backend, rootAddr) for i := 0; i < txCounts; i++ { bp, err := bs.BlockProposal() require.NoError(b, err) res := testutils.RandomResultFixture(b) bp.AppendTransaction(res) bs.StageBlockProposal(bp) + require.NoError(b, err) } - err := bs.FlushBlockProposal() - require.NoError(b, err) // check the impact of updating block proposal after x number of transactions backend.ResetStats() @@ -36,8 +35,6 @@ func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { res := testutils.RandomResultFixture(b) bp.AppendTransaction(res) bs.StageBlockProposal(bp) - err = bs.FlushBlockProposal() - require.NoError(b, err) b.ReportMetric(float64(time.Since(startTime).Nanoseconds()), "proposal_update_time_ns") b.ReportMetric(float64(backend.TotalBytesRead()), "proposal_update_bytes_read") diff --git a/fvm/evm/handler/blockstore_test.go b/fvm/environment/evm_block_store_test.go similarity index 87% rename from fvm/evm/handler/blockstore_test.go rename to fvm/environment/evm_block_store_test.go index ff0722020a7..72304fe3256 100644 --- a/fvm/evm/handler/blockstore_test.go +++ b/fvm/environment/evm_block_store_test.go @@ -1,4 +1,4 @@ -package handler_test +package environment_test import ( "math/big" @@ -7,7 +7,7 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" - "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/environment" "github.com/onflow/flow-go/fvm/evm/testutils" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" @@ -18,7 +18,7 @@ func TestBlockStore(t *testing.T) { var chainID = flow.Testnet testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { - bs := handler.NewBlockStore(chainID, backend, root) + bs := environment.NewBlockStore(chainID, backend, backend, backend, root) // check the Genesis block b, err := bs.LatestBlock() @@ -44,9 +44,10 @@ func TestBlockStore(t *testing.T) { // update the block proposal bp.TotalGasUsed += 100 bs.StageBlockProposal(bp) + err = bs.FlushBlockProposal() + require.NoError(t, err) - // reset the bs and check if it still return the block proposal - bs = handler.NewBlockStore(chainID, backend, root) + bs = environment.NewBlockStore(chainID, backend, backend, backend, root) retbp, err = bs.BlockProposal() require.NoError(t, err) require.Equal(t, bp, retbp) @@ -55,6 +56,8 @@ func TestBlockStore(t *testing.T) { supply := big.NewInt(100) bp.TotalSupply = supply bs.StageBlockProposal(bp) + err = bs.FlushBlockProposal() + require.NoError(t, err) // this should still return the genesis block retb, err := bs.LatestBlock() require.NoError(t, err) diff --git a/fvm/environment/facade_env.go b/fvm/environment/facade_env.go index e69a1175371..c5bbd4d606b 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -3,11 +3,14 @@ package environment import ( "context" + gocommon "github.com/ethereum/go-ethereum/common" "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" + "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/state" @@ -50,8 +53,8 @@ type facadeEnvironment struct { *ContractReader ContractUpdater - BlockProposalCache *Programs + EVMBlockStore EVMBlockStore accounts Accounts txnState storage.TransactionPreparer @@ -147,8 +150,8 @@ func newFacadeEnvironment( accounts, common.Address(sc.Crypto.Address), ), - ContractUpdater: NoContractUpdater{}, - BlockProposalCache: &blockProposalCache{}, + ContractUpdater: NoContractUpdater{}, + EVMBlockStore: NoEVMBlockStore{}, Programs: NewPrograms( tracer, meter, @@ -202,6 +205,13 @@ func NewScriptEnv( params.ScriptInfoParams.ID[:], ) env.addParseRestrictedChecks() + env.EVMBlockStore = NewBlockStore( + params.Chain.ChainID(), + env.ValueStore, + env.BlockInfo, + env.RandomGenerator, + evm.StorageAccountAddress(params.Chain.ChainID()), + ) return env } @@ -272,6 +282,14 @@ func NewTransactionEnvironment( params.TransactionInfoParams.RandomSourceHistoryCallAllowed, ) + env.EVMBlockStore = NewBlockStore( + params.Chain.ChainID(), + env.ValueStore, + env.BlockInfo, + env.RandomGenerator, + evm.StorageAccountAddress(params.Chain.ChainID()), + ) + env.addParseRestrictedChecks() return env @@ -333,16 +351,50 @@ func (env *facadeEnvironment) FlushPendingUpdates() ( ContractUpdates, error, ) { - if err := env.BlockProposalCache.FlushBlockProposal(); err != nil { + updates, err := env.ContractUpdater.Commit() + if err != nil { + return ContractUpdates{}, err + } + err = env.EVMBlockStore.FlushBlockProposal() + if err != nil { return ContractUpdates{}, err } - return env.ContractUpdater.Commit() + return updates, nil } func (env *facadeEnvironment) Reset() { env.ContractUpdater.Reset() env.EventEmitter.Reset() env.Programs.Reset() + env.EVMBlockStore.ResetBlockProposal() +} + +func (env *facadeEnvironment) BlockHash(height uint64) (gocommon.Hash, error) { + return env.EVMBlockStore.BlockHash(height) +} + +func (env *facadeEnvironment) BlockProposal() (*types.BlockProposal, error) { + return env.EVMBlockStore.BlockProposal() +} + +func (env *facadeEnvironment) StageBlockProposal(bp *types.BlockProposal) { + env.EVMBlockStore.StageBlockProposal(bp) +} + +func (env *facadeEnvironment) CommitBlockProposal(bp *types.BlockProposal) error { + return env.EVMBlockStore.CommitBlockProposal(bp) +} + +func (env *facadeEnvironment) FlushBlockProposal() error { + return env.EVMBlockStore.FlushBlockProposal() +} + +func (env *facadeEnvironment) LatestBlock() (*types.Block, error) { + return env.EVMBlockStore.LatestBlock() +} + +func (env *facadeEnvironment) ResetBlockProposal() { + env.EVMBlockStore.ResetBlockProposal() } // Miscellaneous Cadence runtime.Interface API diff --git a/fvm/environment/mock/block_proposal_cache.go b/fvm/environment/mock/block_proposal_cache.go deleted file mode 100644 index 5d72e8bac44..00000000000 --- a/fvm/environment/mock/block_proposal_cache.go +++ /dev/null @@ -1,206 +0,0 @@ -// Code generated by mockery; DO NOT EDIT. -// github.com/vektra/mockery -// template: testify - -package mock - -import ( - mock "github.com/stretchr/testify/mock" -) - -// NewBlockProposalCache creates a new instance of BlockProposalCache. 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 NewBlockProposalCache(t interface { - mock.TestingT - Cleanup(func()) -}) *BlockProposalCache { - mock := &BlockProposalCache{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} - -// BlockProposalCache is an autogenerated mock type for the BlockProposalCache type -type BlockProposalCache struct { - mock.Mock -} - -type BlockProposalCache_Expecter struct { - mock *mock.Mock -} - -func (_m *BlockProposalCache) EXPECT() *BlockProposalCache_Expecter { - return &BlockProposalCache_Expecter{mock: &_m.Mock} -} - -// CacheBlockProposal provides a mock function for the type BlockProposalCache -func (_mock *BlockProposalCache) CacheBlockProposal(v any) { - _mock.Called(v) - return -} - -// BlockProposalCache_CacheBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheBlockProposal' -type BlockProposalCache_CacheBlockProposal_Call struct { - *mock.Call -} - -// CacheBlockProposal is a helper method to define mock.On call -// - v any -func (_e *BlockProposalCache_Expecter) CacheBlockProposal(v interface{}) *BlockProposalCache_CacheBlockProposal_Call { - return &BlockProposalCache_CacheBlockProposal_Call{Call: _e.mock.On("CacheBlockProposal", v)} -} - -func (_c *BlockProposalCache_CacheBlockProposal_Call) Run(run func(v any)) *BlockProposalCache_CacheBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 any - if args[0] != nil { - arg0 = args[0].(any) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *BlockProposalCache_CacheBlockProposal_Call) Return() *BlockProposalCache_CacheBlockProposal_Call { - _c.Call.Return() - return _c -} - -func (_c *BlockProposalCache_CacheBlockProposal_Call) RunAndReturn(run func(v any)) *BlockProposalCache_CacheBlockProposal_Call { - _c.Run(run) - return _c -} - -// CachedBlockProposal provides a mock function for the type BlockProposalCache -func (_mock *BlockProposalCache) CachedBlockProposal() any { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for CachedBlockProposal") - } - - var r0 any - if returnFunc, ok := ret.Get(0).(func() any); ok { - r0 = returnFunc() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(any) - } - } - return r0 -} - -// BlockProposalCache_CachedBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CachedBlockProposal' -type BlockProposalCache_CachedBlockProposal_Call struct { - *mock.Call -} - -// CachedBlockProposal is a helper method to define mock.On call -func (_e *BlockProposalCache_Expecter) CachedBlockProposal() *BlockProposalCache_CachedBlockProposal_Call { - return &BlockProposalCache_CachedBlockProposal_Call{Call: _e.mock.On("CachedBlockProposal")} -} - -func (_c *BlockProposalCache_CachedBlockProposal_Call) Run(run func()) *BlockProposalCache_CachedBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *BlockProposalCache_CachedBlockProposal_Call) Return(v any) *BlockProposalCache_CachedBlockProposal_Call { - _c.Call.Return(v) - return _c -} - -func (_c *BlockProposalCache_CachedBlockProposal_Call) RunAndReturn(run func() any) *BlockProposalCache_CachedBlockProposal_Call { - _c.Call.Return(run) - return _c -} - -// FlushBlockProposal provides a mock function for the type BlockProposalCache -func (_mock *BlockProposalCache) FlushBlockProposal() error { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for FlushBlockProposal") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func() error); ok { - r0 = returnFunc() - } else { - r0 = ret.Error(0) - } - return r0 -} - -// BlockProposalCache_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' -type BlockProposalCache_FlushBlockProposal_Call struct { - *mock.Call -} - -// FlushBlockProposal is a helper method to define mock.On call -func (_e *BlockProposalCache_Expecter) FlushBlockProposal() *BlockProposalCache_FlushBlockProposal_Call { - return &BlockProposalCache_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} -} - -func (_c *BlockProposalCache_FlushBlockProposal_Call) Run(run func()) *BlockProposalCache_FlushBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *BlockProposalCache_FlushBlockProposal_Call) Return(err error) *BlockProposalCache_FlushBlockProposal_Call { - _c.Call.Return(err) - return _c -} - -func (_c *BlockProposalCache_FlushBlockProposal_Call) RunAndReturn(run func() error) *BlockProposalCache_FlushBlockProposal_Call { - _c.Call.Return(run) - return _c -} - -// SetBlockProposalFlusher provides a mock function for the type BlockProposalCache -func (_mock *BlockProposalCache) SetBlockProposalFlusher(fn func() error) { - _mock.Called(fn) - return -} - -// BlockProposalCache_SetBlockProposalFlusher_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetBlockProposalFlusher' -type BlockProposalCache_SetBlockProposalFlusher_Call struct { - *mock.Call -} - -// SetBlockProposalFlusher is a helper method to define mock.On call -// - fn func() error -func (_e *BlockProposalCache_Expecter) SetBlockProposalFlusher(fn interface{}) *BlockProposalCache_SetBlockProposalFlusher_Call { - return &BlockProposalCache_SetBlockProposalFlusher_Call{Call: _e.mock.On("SetBlockProposalFlusher", fn)} -} - -func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) Run(run func(fn func() error)) *BlockProposalCache_SetBlockProposalFlusher_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 func() error - if args[0] != nil { - arg0 = args[0].(func() error) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) Return() *BlockProposalCache_SetBlockProposalFlusher_Call { - _c.Call.Return() - return _c -} - -func (_c *BlockProposalCache_SetBlockProposalFlusher_Call) RunAndReturn(run func(fn func() error)) *BlockProposalCache_SetBlockProposalFlusher_Call { - _c.Run(run) - return _c -} diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index b1b748394bd..f6e463fcdeb 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -7,14 +7,16 @@ package mock import ( "time" + "github.com/ethereum/go-ethereum/common" "github.com/onflow/atree" "github.com/onflow/cadence" "github.com/onflow/cadence/ast" - "github.com/onflow/cadence/common" + common0 "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/sema" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/tracing" "github.com/onflow/flow-go/model/flow" @@ -518,134 +520,165 @@ func (_c *Environment_BLSVerifyPOP_Call) RunAndReturn(run func(publicKey *runtim return _c } -// BorrowCadenceRuntime provides a mock function for the type Environment -func (_mock *Environment) BorrowCadenceRuntime() environment.ReusableCadenceRuntime { - ret := _mock.Called() +// BlockHash provides a mock function for the type Environment +func (_mock *Environment) BlockHash(height uint64) (common.Hash, error) { + ret := _mock.Called(height) if len(ret) == 0 { - panic("no return value specified for BorrowCadenceRuntime") + panic("no return value specified for BlockHash") } - var r0 environment.ReusableCadenceRuntime - if returnFunc, ok := ret.Get(0).(func() environment.ReusableCadenceRuntime); ok { - r0 = returnFunc() + var r0 common.Hash + var r1 error + if returnFunc, ok := ret.Get(0).(func(uint64) (common.Hash, error)); ok { + return returnFunc(height) + } + if returnFunc, ok := ret.Get(0).(func(uint64) common.Hash); ok { + r0 = returnFunc(height) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(environment.ReusableCadenceRuntime) + r0 = ret.Get(0).(common.Hash) } } - return r0 + if returnFunc, ok := ret.Get(1).(func(uint64) error); ok { + r1 = returnFunc(height) + } else { + r1 = ret.Error(1) + } + return r0, r1 } -// Environment_BorrowCadenceRuntime_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BorrowCadenceRuntime' -type Environment_BorrowCadenceRuntime_Call struct { +// Environment_BlockHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockHash' +type Environment_BlockHash_Call struct { *mock.Call } -// BorrowCadenceRuntime is a helper method to define mock.On call -func (_e *Environment_Expecter) BorrowCadenceRuntime() *Environment_BorrowCadenceRuntime_Call { - return &Environment_BorrowCadenceRuntime_Call{Call: _e.mock.On("BorrowCadenceRuntime")} +// BlockHash is a helper method to define mock.On call +// - height uint64 +func (_e *Environment_Expecter) BlockHash(height interface{}) *Environment_BlockHash_Call { + return &Environment_BlockHash_Call{Call: _e.mock.On("BlockHash", height)} } -func (_c *Environment_BorrowCadenceRuntime_Call) Run(run func()) *Environment_BorrowCadenceRuntime_Call { +func (_c *Environment_BlockHash_Call) Run(run func(height uint64)) *Environment_BlockHash_Call { _c.Call.Run(func(args mock.Arguments) { - run() + var arg0 uint64 + if args[0] != nil { + arg0 = args[0].(uint64) + } + run( + arg0, + ) }) return _c } -func (_c *Environment_BorrowCadenceRuntime_Call) Return(reusableCadenceRuntime environment.ReusableCadenceRuntime) *Environment_BorrowCadenceRuntime_Call { - _c.Call.Return(reusableCadenceRuntime) +func (_c *Environment_BlockHash_Call) Return(hash common.Hash, err error) *Environment_BlockHash_Call { + _c.Call.Return(hash, err) return _c } -func (_c *Environment_BorrowCadenceRuntime_Call) RunAndReturn(run func() environment.ReusableCadenceRuntime) *Environment_BorrowCadenceRuntime_Call { +func (_c *Environment_BlockHash_Call) RunAndReturn(run func(height uint64) (common.Hash, error)) *Environment_BlockHash_Call { _c.Call.Return(run) return _c } -// CacheBlockProposal provides a mock function for the type Environment -func (_mock *Environment) CacheBlockProposal(v any) { - _mock.Called(v) - return +// BlockProposal provides a mock function for the type Environment +func (_mock *Environment) BlockProposal() (*types.BlockProposal, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for BlockProposal") + } + + var r0 *types.BlockProposal + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*types.BlockProposal, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *types.BlockProposal); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.BlockProposal) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 } -// Environment_CacheBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CacheBlockProposal' -type Environment_CacheBlockProposal_Call struct { +// Environment_BlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockProposal' +type Environment_BlockProposal_Call struct { *mock.Call } -// CacheBlockProposal is a helper method to define mock.On call -// - v any -func (_e *Environment_Expecter) CacheBlockProposal(v interface{}) *Environment_CacheBlockProposal_Call { - return &Environment_CacheBlockProposal_Call{Call: _e.mock.On("CacheBlockProposal", v)} +// BlockProposal is a helper method to define mock.On call +func (_e *Environment_Expecter) BlockProposal() *Environment_BlockProposal_Call { + return &Environment_BlockProposal_Call{Call: _e.mock.On("BlockProposal")} } -func (_c *Environment_CacheBlockProposal_Call) Run(run func(v any)) *Environment_CacheBlockProposal_Call { +func (_c *Environment_BlockProposal_Call) Run(run func()) *Environment_BlockProposal_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 any - if args[0] != nil { - arg0 = args[0].(any) - } - run( - arg0, - ) + run() }) return _c } -func (_c *Environment_CacheBlockProposal_Call) Return() *Environment_CacheBlockProposal_Call { - _c.Call.Return() +func (_c *Environment_BlockProposal_Call) Return(blockProposal *types.BlockProposal, err error) *Environment_BlockProposal_Call { + _c.Call.Return(blockProposal, err) return _c } -func (_c *Environment_CacheBlockProposal_Call) RunAndReturn(run func(v any)) *Environment_CacheBlockProposal_Call { - _c.Run(run) +func (_c *Environment_BlockProposal_Call) RunAndReturn(run func() (*types.BlockProposal, error)) *Environment_BlockProposal_Call { + _c.Call.Return(run) return _c } -// CachedBlockProposal provides a mock function for the type Environment -func (_mock *Environment) CachedBlockProposal() any { +// BorrowCadenceRuntime provides a mock function for the type Environment +func (_mock *Environment) BorrowCadenceRuntime() environment.ReusableCadenceRuntime { ret := _mock.Called() if len(ret) == 0 { - panic("no return value specified for CachedBlockProposal") + panic("no return value specified for BorrowCadenceRuntime") } - var r0 any - if returnFunc, ok := ret.Get(0).(func() any); ok { + var r0 environment.ReusableCadenceRuntime + if returnFunc, ok := ret.Get(0).(func() environment.ReusableCadenceRuntime); ok { r0 = returnFunc() } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(any) + r0 = ret.Get(0).(environment.ReusableCadenceRuntime) } } return r0 } -// Environment_CachedBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CachedBlockProposal' -type Environment_CachedBlockProposal_Call struct { +// Environment_BorrowCadenceRuntime_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BorrowCadenceRuntime' +type Environment_BorrowCadenceRuntime_Call struct { *mock.Call } -// CachedBlockProposal is a helper method to define mock.On call -func (_e *Environment_Expecter) CachedBlockProposal() *Environment_CachedBlockProposal_Call { - return &Environment_CachedBlockProposal_Call{Call: _e.mock.On("CachedBlockProposal")} +// BorrowCadenceRuntime is a helper method to define mock.On call +func (_e *Environment_Expecter) BorrowCadenceRuntime() *Environment_BorrowCadenceRuntime_Call { + return &Environment_BorrowCadenceRuntime_Call{Call: _e.mock.On("BorrowCadenceRuntime")} } -func (_c *Environment_CachedBlockProposal_Call) Run(run func()) *Environment_CachedBlockProposal_Call { +func (_c *Environment_BorrowCadenceRuntime_Call) Run(run func()) *Environment_BorrowCadenceRuntime_Call { _c.Call.Run(func(args mock.Arguments) { run() }) return _c } -func (_c *Environment_CachedBlockProposal_Call) Return(v any) *Environment_CachedBlockProposal_Call { - _c.Call.Return(v) +func (_c *Environment_BorrowCadenceRuntime_Call) Return(reusableCadenceRuntime environment.ReusableCadenceRuntime) *Environment_BorrowCadenceRuntime_Call { + _c.Call.Return(reusableCadenceRuntime) return _c } -func (_c *Environment_CachedBlockProposal_Call) RunAndReturn(run func() any) *Environment_CachedBlockProposal_Call { +func (_c *Environment_BorrowCadenceRuntime_Call) RunAndReturn(run func() environment.ReusableCadenceRuntime) *Environment_BorrowCadenceRuntime_Call { _c.Call.Return(run) return _c } @@ -724,8 +757,59 @@ func (_c *Environment_CheckPayerBalanceAndGetMaxTxFees_Call) RunAndReturn(run fu return _c } +// CommitBlockProposal provides a mock function for the type Environment +func (_mock *Environment) CommitBlockProposal(blockProposal *types.BlockProposal) error { + ret := _mock.Called(blockProposal) + + if len(ret) == 0 { + panic("no return value specified for CommitBlockProposal") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(*types.BlockProposal) error); ok { + r0 = returnFunc(blockProposal) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// Environment_CommitBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommitBlockProposal' +type Environment_CommitBlockProposal_Call struct { + *mock.Call +} + +// CommitBlockProposal is a helper method to define mock.On call +// - blockProposal *types.BlockProposal +func (_e *Environment_Expecter) CommitBlockProposal(blockProposal interface{}) *Environment_CommitBlockProposal_Call { + return &Environment_CommitBlockProposal_Call{Call: _e.mock.On("CommitBlockProposal", blockProposal)} +} + +func (_c *Environment_CommitBlockProposal_Call) Run(run func(blockProposal *types.BlockProposal)) *Environment_CommitBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *types.BlockProposal + if args[0] != nil { + arg0 = args[0].(*types.BlockProposal) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *Environment_CommitBlockProposal_Call) Return(err error) *Environment_CommitBlockProposal_Call { + _c.Call.Return(err) + return _c +} + +func (_c *Environment_CommitBlockProposal_Call) RunAndReturn(run func(blockProposal *types.BlockProposal) error) *Environment_CommitBlockProposal_Call { + _c.Call.Return(run) + return _c +} + // ComputationAvailable provides a mock function for the type Environment -func (_mock *Environment) ComputationAvailable(computationUsage common.ComputationUsage) bool { +func (_mock *Environment) ComputationAvailable(computationUsage common0.ComputationUsage) bool { ret := _mock.Called(computationUsage) if len(ret) == 0 { @@ -733,7 +817,7 @@ func (_mock *Environment) ComputationAvailable(computationUsage common.Computati } var r0 bool - if returnFunc, ok := ret.Get(0).(func(common.ComputationUsage) bool); ok { + if returnFunc, ok := ret.Get(0).(func(common0.ComputationUsage) bool); ok { r0 = returnFunc(computationUsage) } else { r0 = ret.Get(0).(bool) @@ -747,16 +831,16 @@ type Environment_ComputationAvailable_Call struct { } // ComputationAvailable is a helper method to define mock.On call -// - computationUsage common.ComputationUsage +// - computationUsage common0.ComputationUsage func (_e *Environment_Expecter) ComputationAvailable(computationUsage interface{}) *Environment_ComputationAvailable_Call { return &Environment_ComputationAvailable_Call{Call: _e.mock.On("ComputationAvailable", computationUsage)} } -func (_c *Environment_ComputationAvailable_Call) Run(run func(computationUsage common.ComputationUsage)) *Environment_ComputationAvailable_Call { +func (_c *Environment_ComputationAvailable_Call) Run(run func(computationUsage common0.ComputationUsage)) *Environment_ComputationAvailable_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.ComputationUsage + var arg0 common0.ComputationUsage if args[0] != nil { - arg0 = args[0].(common.ComputationUsage) + arg0 = args[0].(common0.ComputationUsage) } run( arg0, @@ -770,7 +854,7 @@ func (_c *Environment_ComputationAvailable_Call) Return(b bool) *Environment_Com return _c } -func (_c *Environment_ComputationAvailable_Call) RunAndReturn(run func(computationUsage common.ComputationUsage) bool) *Environment_ComputationAvailable_Call { +func (_c *Environment_ComputationAvailable_Call) RunAndReturn(run func(computationUsage common0.ComputationUsage) bool) *Environment_ComputationAvailable_Call { _c.Call.Return(run) return _c } @@ -822,7 +906,7 @@ func (_c *Environment_ComputationIntensities_Call) RunAndReturn(run func() meter } // ComputationRemaining provides a mock function for the type Environment -func (_mock *Environment) ComputationRemaining(kind common.ComputationKind) uint64 { +func (_mock *Environment) ComputationRemaining(kind common0.ComputationKind) uint64 { ret := _mock.Called(kind) if len(ret) == 0 { @@ -830,7 +914,7 @@ func (_mock *Environment) ComputationRemaining(kind common.ComputationKind) uint } var r0 uint64 - if returnFunc, ok := ret.Get(0).(func(common.ComputationKind) uint64); ok { + if returnFunc, ok := ret.Get(0).(func(common0.ComputationKind) uint64); ok { r0 = returnFunc(kind) } else { r0 = ret.Get(0).(uint64) @@ -844,16 +928,16 @@ type Environment_ComputationRemaining_Call struct { } // ComputationRemaining is a helper method to define mock.On call -// - kind common.ComputationKind +// - kind common0.ComputationKind func (_e *Environment_Expecter) ComputationRemaining(kind interface{}) *Environment_ComputationRemaining_Call { return &Environment_ComputationRemaining_Call{Call: _e.mock.On("ComputationRemaining", kind)} } -func (_c *Environment_ComputationRemaining_Call) Run(run func(kind common.ComputationKind)) *Environment_ComputationRemaining_Call { +func (_c *Environment_ComputationRemaining_Call) Run(run func(kind common0.ComputationKind)) *Environment_ComputationRemaining_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.ComputationKind + var arg0 common0.ComputationKind if args[0] != nil { - arg0 = args[0].(common.ComputationKind) + arg0 = args[0].(common0.ComputationKind) } run( arg0, @@ -867,7 +951,7 @@ func (_c *Environment_ComputationRemaining_Call) Return(v uint64) *Environment_C return _c } -func (_c *Environment_ComputationRemaining_Call) RunAndReturn(run func(kind common.ComputationKind) uint64) *Environment_ComputationRemaining_Call { +func (_c *Environment_ComputationRemaining_Call) RunAndReturn(run func(kind common0.ComputationKind) uint64) *Environment_ComputationRemaining_Call { _c.Call.Return(run) return _c } @@ -1518,7 +1602,7 @@ func (_c *Environment_FlushPendingUpdates_Call) RunAndReturn(run func() (environ } // GenerateAccountID provides a mock function for the type Environment -func (_mock *Environment) GenerateAccountID(address common.Address) (uint64, error) { +func (_mock *Environment) GenerateAccountID(address common0.Address) (uint64, error) { ret := _mock.Called(address) if len(ret) == 0 { @@ -1527,15 +1611,15 @@ func (_mock *Environment) GenerateAccountID(address common.Address) (uint64, err var r0 uint64 var r1 error - if returnFunc, ok := ret.Get(0).(func(common.Address) (uint64, error)); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) (uint64, error)); ok { return returnFunc(address) } - if returnFunc, ok := ret.Get(0).(func(common.Address) uint64); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) uint64); ok { r0 = returnFunc(address) } else { r0 = ret.Get(0).(uint64) } - if returnFunc, ok := ret.Get(1).(func(common.Address) error); ok { + if returnFunc, ok := ret.Get(1).(func(common0.Address) error); ok { r1 = returnFunc(address) } else { r1 = ret.Error(1) @@ -1549,16 +1633,16 @@ type Environment_GenerateAccountID_Call struct { } // GenerateAccountID is a helper method to define mock.On call -// - address common.Address +// - address common0.Address func (_e *Environment_Expecter) GenerateAccountID(address interface{}) *Environment_GenerateAccountID_Call { return &Environment_GenerateAccountID_Call{Call: _e.mock.On("GenerateAccountID", address)} } -func (_c *Environment_GenerateAccountID_Call) Run(run func(address common.Address)) *Environment_GenerateAccountID_Call { +func (_c *Environment_GenerateAccountID_Call) Run(run func(address common0.Address)) *Environment_GenerateAccountID_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.Address + var arg0 common0.Address if args[0] != nil { - arg0 = args[0].(common.Address) + arg0 = args[0].(common0.Address) } run( arg0, @@ -1572,7 +1656,7 @@ func (_c *Environment_GenerateAccountID_Call) Return(v uint64, err error) *Envir return _c } -func (_c *Environment_GenerateAccountID_Call) RunAndReturn(run func(address common.Address) (uint64, error)) *Environment_GenerateAccountID_Call { +func (_c *Environment_GenerateAccountID_Call) RunAndReturn(run func(address common0.Address) (uint64, error)) *Environment_GenerateAccountID_Call { _c.Call.Return(run) return _c } @@ -1693,7 +1777,7 @@ func (_c *Environment_GetAccount_Call) RunAndReturn(run func(address flow.Addres } // GetAccountAvailableBalance provides a mock function for the type Environment -func (_mock *Environment) GetAccountAvailableBalance(address common.Address) (uint64, error) { +func (_mock *Environment) GetAccountAvailableBalance(address common0.Address) (uint64, error) { ret := _mock.Called(address) if len(ret) == 0 { @@ -1702,15 +1786,15 @@ func (_mock *Environment) GetAccountAvailableBalance(address common.Address) (ui var r0 uint64 var r1 error - if returnFunc, ok := ret.Get(0).(func(common.Address) (uint64, error)); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) (uint64, error)); ok { return returnFunc(address) } - if returnFunc, ok := ret.Get(0).(func(common.Address) uint64); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) uint64); ok { r0 = returnFunc(address) } else { r0 = ret.Get(0).(uint64) } - if returnFunc, ok := ret.Get(1).(func(common.Address) error); ok { + if returnFunc, ok := ret.Get(1).(func(common0.Address) error); ok { r1 = returnFunc(address) } else { r1 = ret.Error(1) @@ -1724,16 +1808,16 @@ type Environment_GetAccountAvailableBalance_Call struct { } // GetAccountAvailableBalance is a helper method to define mock.On call -// - address common.Address +// - address common0.Address func (_e *Environment_Expecter) GetAccountAvailableBalance(address interface{}) *Environment_GetAccountAvailableBalance_Call { return &Environment_GetAccountAvailableBalance_Call{Call: _e.mock.On("GetAccountAvailableBalance", address)} } -func (_c *Environment_GetAccountAvailableBalance_Call) Run(run func(address common.Address)) *Environment_GetAccountAvailableBalance_Call { +func (_c *Environment_GetAccountAvailableBalance_Call) Run(run func(address common0.Address)) *Environment_GetAccountAvailableBalance_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.Address + var arg0 common0.Address if args[0] != nil { - arg0 = args[0].(common.Address) + arg0 = args[0].(common0.Address) } run( arg0, @@ -1747,13 +1831,13 @@ func (_c *Environment_GetAccountAvailableBalance_Call) Return(value uint64, err return _c } -func (_c *Environment_GetAccountAvailableBalance_Call) RunAndReturn(run func(address common.Address) (uint64, error)) *Environment_GetAccountAvailableBalance_Call { +func (_c *Environment_GetAccountAvailableBalance_Call) RunAndReturn(run func(address common0.Address) (uint64, error)) *Environment_GetAccountAvailableBalance_Call { _c.Call.Return(run) return _c } // GetAccountBalance provides a mock function for the type Environment -func (_mock *Environment) GetAccountBalance(address common.Address) (uint64, error) { +func (_mock *Environment) GetAccountBalance(address common0.Address) (uint64, error) { ret := _mock.Called(address) if len(ret) == 0 { @@ -1762,15 +1846,15 @@ func (_mock *Environment) GetAccountBalance(address common.Address) (uint64, err var r0 uint64 var r1 error - if returnFunc, ok := ret.Get(0).(func(common.Address) (uint64, error)); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) (uint64, error)); ok { return returnFunc(address) } - if returnFunc, ok := ret.Get(0).(func(common.Address) uint64); ok { + if returnFunc, ok := ret.Get(0).(func(common0.Address) uint64); ok { r0 = returnFunc(address) } else { r0 = ret.Get(0).(uint64) } - if returnFunc, ok := ret.Get(1).(func(common.Address) error); ok { + if returnFunc, ok := ret.Get(1).(func(common0.Address) error); ok { r1 = returnFunc(address) } else { r1 = ret.Error(1) @@ -1784,16 +1868,16 @@ type Environment_GetAccountBalance_Call struct { } // GetAccountBalance is a helper method to define mock.On call -// - address common.Address +// - address common0.Address func (_e *Environment_Expecter) GetAccountBalance(address interface{}) *Environment_GetAccountBalance_Call { return &Environment_GetAccountBalance_Call{Call: _e.mock.On("GetAccountBalance", address)} } -func (_c *Environment_GetAccountBalance_Call) Run(run func(address common.Address)) *Environment_GetAccountBalance_Call { +func (_c *Environment_GetAccountBalance_Call) Run(run func(address common0.Address)) *Environment_GetAccountBalance_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.Address + var arg0 common0.Address if args[0] != nil { - arg0 = args[0].(common.Address) + arg0 = args[0].(common0.Address) } run( arg0, @@ -1807,13 +1891,13 @@ func (_c *Environment_GetAccountBalance_Call) Return(value uint64, err error) *E return _c } -func (_c *Environment_GetAccountBalance_Call) RunAndReturn(run func(address common.Address) (uint64, error)) *Environment_GetAccountBalance_Call { +func (_c *Environment_GetAccountBalance_Call) RunAndReturn(run func(address common0.Address) (uint64, error)) *Environment_GetAccountBalance_Call { _c.Call.Return(run) return _c } // GetAccountContractCode provides a mock function for the type Environment -func (_mock *Environment) GetAccountContractCode(location common.AddressLocation) ([]byte, error) { +func (_mock *Environment) GetAccountContractCode(location common0.AddressLocation) ([]byte, error) { ret := _mock.Called(location) if len(ret) == 0 { @@ -1822,17 +1906,17 @@ func (_mock *Environment) GetAccountContractCode(location common.AddressLocation var r0 []byte var r1 error - if returnFunc, ok := ret.Get(0).(func(common.AddressLocation) ([]byte, error)); ok { + if returnFunc, ok := ret.Get(0).(func(common0.AddressLocation) ([]byte, error)); ok { return returnFunc(location) } - if returnFunc, ok := ret.Get(0).(func(common.AddressLocation) []byte); ok { + if returnFunc, ok := ret.Get(0).(func(common0.AddressLocation) []byte); ok { r0 = returnFunc(location) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]byte) } } - if returnFunc, ok := ret.Get(1).(func(common.AddressLocation) error); ok { + if returnFunc, ok := ret.Get(1).(func(common0.AddressLocation) error); ok { r1 = returnFunc(location) } else { r1 = ret.Error(1) @@ -1846,16 +1930,16 @@ type Environment_GetAccountContractCode_Call struct { } // GetAccountContractCode is a helper method to define mock.On call -// - location common.AddressLocation +// - location common0.AddressLocation func (_e *Environment_Expecter) GetAccountContractCode(location interface{}) *Environment_GetAccountContractCode_Call { return &Environment_GetAccountContractCode_Call{Call: _e.mock.On("GetAccountContractCode", location)} } -func (_c *Environment_GetAccountContractCode_Call) Run(run func(location common.AddressLocation)) *Environment_GetAccountContractCode_Call { +func (_c *Environment_GetAccountContractCode_Call) Run(run func(location common0.AddressLocation)) *Environment_GetAccountContractCode_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.AddressLocation + var arg0 common0.AddressLocation if args[0] != nil { - arg0 = args[0].(common.AddressLocation) + arg0 = args[0].(common0.AddressLocation) } run( arg0, @@ -1869,7 +1953,7 @@ func (_c *Environment_GetAccountContractCode_Call) Return(code []byte, err error return _c } -func (_c *Environment_GetAccountContractCode_Call) RunAndReturn(run func(location common.AddressLocation) ([]byte, error)) *Environment_GetAccountContractCode_Call { +func (_c *Environment_GetAccountContractCode_Call) RunAndReturn(run func(location common0.AddressLocation) ([]byte, error)) *Environment_GetAccountContractCode_Call { _c.Call.Return(run) return _c } @@ -2795,6 +2879,61 @@ func (_c *Environment_IsServiceAccountAuthorizer_Call) RunAndReturn(run func() b return _c } +// LatestBlock provides a mock function for the type Environment +func (_mock *Environment) LatestBlock() (*types.Block, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for LatestBlock") + } + + var r0 *types.Block + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*types.Block, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *types.Block); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// Environment_LatestBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestBlock' +type Environment_LatestBlock_Call struct { + *mock.Call +} + +// LatestBlock is a helper method to define mock.On call +func (_e *Environment_Expecter) LatestBlock() *Environment_LatestBlock_Call { + return &Environment_LatestBlock_Call{Call: _e.mock.On("LatestBlock")} +} + +func (_c *Environment_LatestBlock_Call) Run(run func()) *Environment_LatestBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Environment_LatestBlock_Call) Return(block *types.Block, err error) *Environment_LatestBlock_Call { + _c.Call.Return(block, err) + return _c +} + +func (_c *Environment_LatestBlock_Call) RunAndReturn(run func() (*types.Block, error)) *Environment_LatestBlock_Call { + _c.Call.Return(run) + return _c +} + // LimitAccountStorage provides a mock function for the type Environment func (_mock *Environment) LimitAccountStorage() bool { ret := _mock.Called() @@ -2983,7 +3122,7 @@ func (_c *Environment_MemoryUsed_Call) RunAndReturn(run func() (uint64, error)) } // MeterComputation provides a mock function for the type Environment -func (_mock *Environment) MeterComputation(usage common.ComputationUsage) error { +func (_mock *Environment) MeterComputation(usage common0.ComputationUsage) error { ret := _mock.Called(usage) if len(ret) == 0 { @@ -2991,7 +3130,7 @@ func (_mock *Environment) MeterComputation(usage common.ComputationUsage) error } var r0 error - if returnFunc, ok := ret.Get(0).(func(common.ComputationUsage) error); ok { + if returnFunc, ok := ret.Get(0).(func(common0.ComputationUsage) error); ok { r0 = returnFunc(usage) } else { r0 = ret.Error(0) @@ -3005,16 +3144,16 @@ type Environment_MeterComputation_Call struct { } // MeterComputation is a helper method to define mock.On call -// - usage common.ComputationUsage +// - usage common0.ComputationUsage func (_e *Environment_Expecter) MeterComputation(usage interface{}) *Environment_MeterComputation_Call { return &Environment_MeterComputation_Call{Call: _e.mock.On("MeterComputation", usage)} } -func (_c *Environment_MeterComputation_Call) Run(run func(usage common.ComputationUsage)) *Environment_MeterComputation_Call { +func (_c *Environment_MeterComputation_Call) Run(run func(usage common0.ComputationUsage)) *Environment_MeterComputation_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.ComputationUsage + var arg0 common0.ComputationUsage if args[0] != nil { - arg0 = args[0].(common.ComputationUsage) + arg0 = args[0].(common0.ComputationUsage) } run( arg0, @@ -3028,7 +3167,7 @@ func (_c *Environment_MeterComputation_Call) Return(err error) *Environment_Mete return _c } -func (_c *Environment_MeterComputation_Call) RunAndReturn(run func(usage common.ComputationUsage) error) *Environment_MeterComputation_Call { +func (_c *Environment_MeterComputation_Call) RunAndReturn(run func(usage common0.ComputationUsage) error) *Environment_MeterComputation_Call { _c.Call.Return(run) return _c } @@ -3085,7 +3224,7 @@ func (_c *Environment_MeterEmittedEvent_Call) RunAndReturn(run func(byteSize uin } // MeterMemory provides a mock function for the type Environment -func (_mock *Environment) MeterMemory(usage common.MemoryUsage) error { +func (_mock *Environment) MeterMemory(usage common0.MemoryUsage) error { ret := _mock.Called(usage) if len(ret) == 0 { @@ -3093,7 +3232,7 @@ func (_mock *Environment) MeterMemory(usage common.MemoryUsage) error { } var r0 error - if returnFunc, ok := ret.Get(0).(func(common.MemoryUsage) error); ok { + if returnFunc, ok := ret.Get(0).(func(common0.MemoryUsage) error); ok { r0 = returnFunc(usage) } else { r0 = ret.Error(0) @@ -3107,16 +3246,16 @@ type Environment_MeterMemory_Call struct { } // MeterMemory is a helper method to define mock.On call -// - usage common.MemoryUsage +// - usage common0.MemoryUsage func (_e *Environment_Expecter) MeterMemory(usage interface{}) *Environment_MeterMemory_Call { return &Environment_MeterMemory_Call{Call: _e.mock.On("MeterMemory", usage)} } -func (_c *Environment_MeterMemory_Call) Run(run func(usage common.MemoryUsage)) *Environment_MeterMemory_Call { +func (_c *Environment_MeterMemory_Call) Run(run func(usage common0.MemoryUsage)) *Environment_MeterMemory_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.MemoryUsage + var arg0 common0.MemoryUsage if args[0] != nil { - arg0 = args[0].(common.MemoryUsage) + arg0 = args[0].(common0.MemoryUsage) } run( arg0, @@ -3130,7 +3269,7 @@ func (_c *Environment_MeterMemory_Call) Return(err error) *Environment_MeterMemo return _c } -func (_c *Environment_MeterMemory_Call) RunAndReturn(run func(usage common.MemoryUsage) error) *Environment_MeterMemory_Call { +func (_c *Environment_MeterMemory_Call) RunAndReturn(run func(usage common0.MemoryUsage) error) *Environment_MeterMemory_Call { _c.Call.Return(run) return _c } @@ -3398,7 +3537,7 @@ func (_c *Environment_RecordTrace_Call) RunAndReturn(run func(operation string, } // RecoverProgram provides a mock function for the type Environment -func (_mock *Environment) RecoverProgram(program *ast.Program, location common.Location) ([]byte, error) { +func (_mock *Environment) RecoverProgram(program *ast.Program, location common0.Location) ([]byte, error) { ret := _mock.Called(program, location) if len(ret) == 0 { @@ -3407,17 +3546,17 @@ func (_mock *Environment) RecoverProgram(program *ast.Program, location common.L var r0 []byte var r1 error - if returnFunc, ok := ret.Get(0).(func(*ast.Program, common.Location) ([]byte, error)); ok { + if returnFunc, ok := ret.Get(0).(func(*ast.Program, common0.Location) ([]byte, error)); ok { return returnFunc(program, location) } - if returnFunc, ok := ret.Get(0).(func(*ast.Program, common.Location) []byte); ok { + if returnFunc, ok := ret.Get(0).(func(*ast.Program, common0.Location) []byte); ok { r0 = returnFunc(program, location) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]byte) } } - if returnFunc, ok := ret.Get(1).(func(*ast.Program, common.Location) error); ok { + if returnFunc, ok := ret.Get(1).(func(*ast.Program, common0.Location) error); ok { r1 = returnFunc(program, location) } else { r1 = ret.Error(1) @@ -3432,20 +3571,20 @@ type Environment_RecoverProgram_Call struct { // RecoverProgram is a helper method to define mock.On call // - program *ast.Program -// - location common.Location +// - location common0.Location func (_e *Environment_Expecter) RecoverProgram(program interface{}, location interface{}) *Environment_RecoverProgram_Call { return &Environment_RecoverProgram_Call{Call: _e.mock.On("RecoverProgram", program, location)} } -func (_c *Environment_RecoverProgram_Call) Run(run func(program *ast.Program, location common.Location)) *Environment_RecoverProgram_Call { +func (_c *Environment_RecoverProgram_Call) Run(run func(program *ast.Program, location common0.Location)) *Environment_RecoverProgram_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 *ast.Program if args[0] != nil { arg0 = args[0].(*ast.Program) } - var arg1 common.Location + var arg1 common0.Location if args[1] != nil { - arg1 = args[1].(common.Location) + arg1 = args[1].(common0.Location) } run( arg0, @@ -3460,13 +3599,13 @@ func (_c *Environment_RecoverProgram_Call) Return(bytes []byte, err error) *Envi return _c } -func (_c *Environment_RecoverProgram_Call) RunAndReturn(run func(program *ast.Program, location common.Location) ([]byte, error)) *Environment_RecoverProgram_Call { +func (_c *Environment_RecoverProgram_Call) RunAndReturn(run func(program *ast.Program, location common0.Location) ([]byte, error)) *Environment_RecoverProgram_Call { _c.Call.Return(run) return _c } // RemoveAccountContractCode provides a mock function for the type Environment -func (_mock *Environment) RemoveAccountContractCode(location common.AddressLocation) error { +func (_mock *Environment) RemoveAccountContractCode(location common0.AddressLocation) error { ret := _mock.Called(location) if len(ret) == 0 { @@ -3474,7 +3613,7 @@ func (_mock *Environment) RemoveAccountContractCode(location common.AddressLocat } var r0 error - if returnFunc, ok := ret.Get(0).(func(common.AddressLocation) error); ok { + if returnFunc, ok := ret.Get(0).(func(common0.AddressLocation) error); ok { r0 = returnFunc(location) } else { r0 = ret.Error(0) @@ -3488,16 +3627,16 @@ type Environment_RemoveAccountContractCode_Call struct { } // RemoveAccountContractCode is a helper method to define mock.On call -// - location common.AddressLocation +// - location common0.AddressLocation func (_e *Environment_Expecter) RemoveAccountContractCode(location interface{}) *Environment_RemoveAccountContractCode_Call { return &Environment_RemoveAccountContractCode_Call{Call: _e.mock.On("RemoveAccountContractCode", location)} } -func (_c *Environment_RemoveAccountContractCode_Call) Run(run func(location common.AddressLocation)) *Environment_RemoveAccountContractCode_Call { +func (_c *Environment_RemoveAccountContractCode_Call) Run(run func(location common0.AddressLocation)) *Environment_RemoveAccountContractCode_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.AddressLocation + var arg0 common0.AddressLocation if args[0] != nil { - arg0 = args[0].(common.AddressLocation) + arg0 = args[0].(common0.AddressLocation) } run( arg0, @@ -3511,7 +3650,7 @@ func (_c *Environment_RemoveAccountContractCode_Call) Return(err error) *Environ return _c } -func (_c *Environment_RemoveAccountContractCode_Call) RunAndReturn(run func(location common.AddressLocation) error) *Environment_RemoveAccountContractCode_Call { +func (_c *Environment_RemoveAccountContractCode_Call) RunAndReturn(run func(location common0.AddressLocation) error) *Environment_RemoveAccountContractCode_Call { _c.Call.Return(run) return _c } @@ -3549,6 +3688,39 @@ func (_c *Environment_Reset_Call) RunAndReturn(run func()) *Environment_Reset_Ca return _c } +// ResetBlockProposal provides a mock function for the type Environment +func (_mock *Environment) ResetBlockProposal() { + _mock.Called() + return +} + +// Environment_ResetBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBlockProposal' +type Environment_ResetBlockProposal_Call struct { + *mock.Call +} + +// ResetBlockProposal is a helper method to define mock.On call +func (_e *Environment_Expecter) ResetBlockProposal() *Environment_ResetBlockProposal_Call { + return &Environment_ResetBlockProposal_Call{Call: _e.mock.On("ResetBlockProposal")} +} + +func (_c *Environment_ResetBlockProposal_Call) Run(run func()) *Environment_ResetBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Environment_ResetBlockProposal_Call) Return() *Environment_ResetBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *Environment_ResetBlockProposal_Call) RunAndReturn(run func()) *Environment_ResetBlockProposal_Call { + _c.Run(run) + return _c +} + // ResolveLocation provides a mock function for the type Environment func (_mock *Environment) ResolveLocation(identifiers []runtime.Identifier, location runtime.Location) ([]runtime.ResolvedLocation, error) { ret := _mock.Called(identifiers, location) @@ -3618,7 +3790,7 @@ func (_c *Environment_ResolveLocation_Call) RunAndReturn(run func(identifiers [] } // ResourceOwnerChanged provides a mock function for the type Environment -func (_mock *Environment) ResourceOwnerChanged(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common.Address, newOwner common.Address) { +func (_mock *Environment) ResourceOwnerChanged(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common0.Address, newOwner common0.Address) { _mock.Called(interpreter1, resource, oldOwner, newOwner) return } @@ -3631,13 +3803,13 @@ type Environment_ResourceOwnerChanged_Call struct { // ResourceOwnerChanged is a helper method to define mock.On call // - interpreter1 *interpreter.Interpreter // - resource *interpreter.CompositeValue -// - oldOwner common.Address -// - newOwner common.Address +// - oldOwner common0.Address +// - newOwner common0.Address func (_e *Environment_Expecter) ResourceOwnerChanged(interpreter1 interface{}, resource interface{}, oldOwner interface{}, newOwner interface{}) *Environment_ResourceOwnerChanged_Call { return &Environment_ResourceOwnerChanged_Call{Call: _e.mock.On("ResourceOwnerChanged", interpreter1, resource, oldOwner, newOwner)} } -func (_c *Environment_ResourceOwnerChanged_Call) Run(run func(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common.Address, newOwner common.Address)) *Environment_ResourceOwnerChanged_Call { +func (_c *Environment_ResourceOwnerChanged_Call) Run(run func(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common0.Address, newOwner common0.Address)) *Environment_ResourceOwnerChanged_Call { _c.Call.Run(func(args mock.Arguments) { var arg0 *interpreter.Interpreter if args[0] != nil { @@ -3647,13 +3819,13 @@ func (_c *Environment_ResourceOwnerChanged_Call) Run(run func(interpreter1 *inte if args[1] != nil { arg1 = args[1].(*interpreter.CompositeValue) } - var arg2 common.Address + var arg2 common0.Address if args[2] != nil { - arg2 = args[2].(common.Address) + arg2 = args[2].(common0.Address) } - var arg3 common.Address + var arg3 common0.Address if args[3] != nil { - arg3 = args[3].(common.Address) + arg3 = args[3].(common0.Address) } run( arg0, @@ -3670,7 +3842,7 @@ func (_c *Environment_ResourceOwnerChanged_Call) Return() *Environment_ResourceO return _c } -func (_c *Environment_ResourceOwnerChanged_Call) RunAndReturn(run func(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common.Address, newOwner common.Address)) *Environment_ResourceOwnerChanged_Call { +func (_c *Environment_ResourceOwnerChanged_Call) RunAndReturn(run func(interpreter1 *interpreter.Interpreter, resource *interpreter.CompositeValue, oldOwner common0.Address, newOwner common0.Address)) *Environment_ResourceOwnerChanged_Call { _c.Run(run) return _c } @@ -4095,46 +4267,6 @@ func (_c *Environment_ServiceEvents_Call) RunAndReturn(run func() flow.EventsLis return _c } -// SetBlockProposalFlusher provides a mock function for the type Environment -func (_mock *Environment) SetBlockProposalFlusher(fn func() error) { - _mock.Called(fn) - return -} - -// Environment_SetBlockProposalFlusher_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetBlockProposalFlusher' -type Environment_SetBlockProposalFlusher_Call struct { - *mock.Call -} - -// SetBlockProposalFlusher is a helper method to define mock.On call -// - fn func() error -func (_e *Environment_Expecter) SetBlockProposalFlusher(fn interface{}) *Environment_SetBlockProposalFlusher_Call { - return &Environment_SetBlockProposalFlusher_Call{Call: _e.mock.On("SetBlockProposalFlusher", fn)} -} - -func (_c *Environment_SetBlockProposalFlusher_Call) Run(run func(fn func() error)) *Environment_SetBlockProposalFlusher_Call { - _c.Call.Run(func(args mock.Arguments) { - var arg0 func() error - if args[0] != nil { - arg0 = args[0].(func() error) - } - run( - arg0, - ) - }) - return _c -} - -func (_c *Environment_SetBlockProposalFlusher_Call) Return() *Environment_SetBlockProposalFlusher_Call { - _c.Call.Return() - return _c -} - -func (_c *Environment_SetBlockProposalFlusher_Call) RunAndReturn(run func(fn func() error)) *Environment_SetBlockProposalFlusher_Call { - _c.Run(run) - return _c -} - // SetNumberOfDeployedCOAs provides a mock function for the type Environment func (_mock *Environment) SetNumberOfDeployedCOAs(count uint64) { _mock.Called(count) @@ -4238,6 +4370,46 @@ func (_c *Environment_SetValue_Call) RunAndReturn(run func(owner []byte, key []b return _c } +// StageBlockProposal provides a mock function for the type Environment +func (_mock *Environment) StageBlockProposal(blockProposal *types.BlockProposal) { + _mock.Called(blockProposal) + return +} + +// Environment_StageBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StageBlockProposal' +type Environment_StageBlockProposal_Call struct { + *mock.Call +} + +// StageBlockProposal is a helper method to define mock.On call +// - blockProposal *types.BlockProposal +func (_e *Environment_Expecter) StageBlockProposal(blockProposal interface{}) *Environment_StageBlockProposal_Call { + return &Environment_StageBlockProposal_Call{Call: _e.mock.On("StageBlockProposal", blockProposal)} +} + +func (_c *Environment_StageBlockProposal_Call) Run(run func(blockProposal *types.BlockProposal)) *Environment_StageBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *types.BlockProposal + if args[0] != nil { + arg0 = args[0].(*types.BlockProposal) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *Environment_StageBlockProposal_Call) Return() *Environment_StageBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *Environment_StageBlockProposal_Call) RunAndReturn(run func(blockProposal *types.BlockProposal)) *Environment_StageBlockProposal_Call { + _c.Run(run) + return _c +} + // StartChildSpan provides a mock function for the type Environment func (_mock *Environment) StartChildSpan(name trace.SpanName, options ...trace0.SpanStartOption) tracing.TracerSpan { // trace0.SpanStartOption @@ -4487,7 +4659,7 @@ func (_c *Environment_TxIndex_Call) RunAndReturn(run func() uint32) *Environment } // UpdateAccountContractCode provides a mock function for the type Environment -func (_mock *Environment) UpdateAccountContractCode(location common.AddressLocation, code []byte) error { +func (_mock *Environment) UpdateAccountContractCode(location common0.AddressLocation, code []byte) error { ret := _mock.Called(location, code) if len(ret) == 0 { @@ -4495,7 +4667,7 @@ func (_mock *Environment) UpdateAccountContractCode(location common.AddressLocat } var r0 error - if returnFunc, ok := ret.Get(0).(func(common.AddressLocation, []byte) error); ok { + if returnFunc, ok := ret.Get(0).(func(common0.AddressLocation, []byte) error); ok { r0 = returnFunc(location, code) } else { r0 = ret.Error(0) @@ -4509,17 +4681,17 @@ type Environment_UpdateAccountContractCode_Call struct { } // UpdateAccountContractCode is a helper method to define mock.On call -// - location common.AddressLocation +// - location common0.AddressLocation // - code []byte func (_e *Environment_Expecter) UpdateAccountContractCode(location interface{}, code interface{}) *Environment_UpdateAccountContractCode_Call { return &Environment_UpdateAccountContractCode_Call{Call: _e.mock.On("UpdateAccountContractCode", location, code)} } -func (_c *Environment_UpdateAccountContractCode_Call) Run(run func(location common.AddressLocation, code []byte)) *Environment_UpdateAccountContractCode_Call { +func (_c *Environment_UpdateAccountContractCode_Call) Run(run func(location common0.AddressLocation, code []byte)) *Environment_UpdateAccountContractCode_Call { _c.Call.Run(func(args mock.Arguments) { - var arg0 common.AddressLocation + var arg0 common0.AddressLocation if args[0] != nil { - arg0 = args[0].(common.AddressLocation) + arg0 = args[0].(common0.AddressLocation) } var arg1 []byte if args[1] != nil { @@ -4538,7 +4710,7 @@ func (_c *Environment_UpdateAccountContractCode_Call) Return(err error) *Environ return _c } -func (_c *Environment_UpdateAccountContractCode_Call) RunAndReturn(run func(location common.AddressLocation, code []byte) error) *Environment_UpdateAccountContractCode_Call { +func (_c *Environment_UpdateAccountContractCode_Call) RunAndReturn(run func(location common0.AddressLocation, code []byte) error) *Environment_UpdateAccountContractCode_Call { _c.Call.Return(run) return _c } diff --git a/fvm/environment/mock/evm_block_store.go b/fvm/environment/mock/evm_block_store.go new file mode 100644 index 00000000000..7ebe23991d0 --- /dev/null +++ b/fvm/environment/mock/evm_block_store.go @@ -0,0 +1,378 @@ +// Code generated by mockery; DO NOT EDIT. +// github.com/vektra/mockery +// template: testify + +package mock + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/onflow/flow-go/fvm/evm/types" + mock "github.com/stretchr/testify/mock" +) + +// NewEVMBlockStore creates a new instance of EVMBlockStore. 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 NewEVMBlockStore(t interface { + mock.TestingT + Cleanup(func()) +}) *EVMBlockStore { + mock := &EVMBlockStore{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} + +// EVMBlockStore is an autogenerated mock type for the EVMBlockStore type +type EVMBlockStore struct { + mock.Mock +} + +type EVMBlockStore_Expecter struct { + mock *mock.Mock +} + +func (_m *EVMBlockStore) EXPECT() *EVMBlockStore_Expecter { + return &EVMBlockStore_Expecter{mock: &_m.Mock} +} + +// BlockHash provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) BlockHash(height uint64) (common.Hash, error) { + ret := _mock.Called(height) + + if len(ret) == 0 { + panic("no return value specified for BlockHash") + } + + var r0 common.Hash + var r1 error + if returnFunc, ok := ret.Get(0).(func(uint64) (common.Hash, error)); ok { + return returnFunc(height) + } + if returnFunc, ok := ret.Get(0).(func(uint64) common.Hash); ok { + r0 = returnFunc(height) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(common.Hash) + } + } + if returnFunc, ok := ret.Get(1).(func(uint64) error); ok { + r1 = returnFunc(height) + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// EVMBlockStore_BlockHash_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockHash' +type EVMBlockStore_BlockHash_Call struct { + *mock.Call +} + +// BlockHash is a helper method to define mock.On call +// - height uint64 +func (_e *EVMBlockStore_Expecter) BlockHash(height interface{}) *EVMBlockStore_BlockHash_Call { + return &EVMBlockStore_BlockHash_Call{Call: _e.mock.On("BlockHash", height)} +} + +func (_c *EVMBlockStore_BlockHash_Call) Run(run func(height uint64)) *EVMBlockStore_BlockHash_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 uint64 + if args[0] != nil { + arg0 = args[0].(uint64) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *EVMBlockStore_BlockHash_Call) Return(hash common.Hash, err error) *EVMBlockStore_BlockHash_Call { + _c.Call.Return(hash, err) + return _c +} + +func (_c *EVMBlockStore_BlockHash_Call) RunAndReturn(run func(height uint64) (common.Hash, error)) *EVMBlockStore_BlockHash_Call { + _c.Call.Return(run) + return _c +} + +// BlockProposal provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) BlockProposal() (*types.BlockProposal, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for BlockProposal") + } + + var r0 *types.BlockProposal + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*types.BlockProposal, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *types.BlockProposal); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.BlockProposal) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// EVMBlockStore_BlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'BlockProposal' +type EVMBlockStore_BlockProposal_Call struct { + *mock.Call +} + +// BlockProposal is a helper method to define mock.On call +func (_e *EVMBlockStore_Expecter) BlockProposal() *EVMBlockStore_BlockProposal_Call { + return &EVMBlockStore_BlockProposal_Call{Call: _e.mock.On("BlockProposal")} +} + +func (_c *EVMBlockStore_BlockProposal_Call) Run(run func()) *EVMBlockStore_BlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EVMBlockStore_BlockProposal_Call) Return(blockProposal *types.BlockProposal, err error) *EVMBlockStore_BlockProposal_Call { + _c.Call.Return(blockProposal, err) + return _c +} + +func (_c *EVMBlockStore_BlockProposal_Call) RunAndReturn(run func() (*types.BlockProposal, error)) *EVMBlockStore_BlockProposal_Call { + _c.Call.Return(run) + return _c +} + +// CommitBlockProposal provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) CommitBlockProposal(blockProposal *types.BlockProposal) error { + ret := _mock.Called(blockProposal) + + if len(ret) == 0 { + panic("no return value specified for CommitBlockProposal") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func(*types.BlockProposal) error); ok { + r0 = returnFunc(blockProposal) + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EVMBlockStore_CommitBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CommitBlockProposal' +type EVMBlockStore_CommitBlockProposal_Call struct { + *mock.Call +} + +// CommitBlockProposal is a helper method to define mock.On call +// - blockProposal *types.BlockProposal +func (_e *EVMBlockStore_Expecter) CommitBlockProposal(blockProposal interface{}) *EVMBlockStore_CommitBlockProposal_Call { + return &EVMBlockStore_CommitBlockProposal_Call{Call: _e.mock.On("CommitBlockProposal", blockProposal)} +} + +func (_c *EVMBlockStore_CommitBlockProposal_Call) Run(run func(blockProposal *types.BlockProposal)) *EVMBlockStore_CommitBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *types.BlockProposal + if args[0] != nil { + arg0 = args[0].(*types.BlockProposal) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *EVMBlockStore_CommitBlockProposal_Call) Return(err error) *EVMBlockStore_CommitBlockProposal_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EVMBlockStore_CommitBlockProposal_Call) RunAndReturn(run func(blockProposal *types.BlockProposal) error) *EVMBlockStore_CommitBlockProposal_Call { + _c.Call.Return(run) + return _c +} + +// FlushBlockProposal provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) FlushBlockProposal() error { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for FlushBlockProposal") + } + + var r0 error + if returnFunc, ok := ret.Get(0).(func() error); ok { + r0 = returnFunc() + } else { + r0 = ret.Error(0) + } + return r0 +} + +// EVMBlockStore_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' +type EVMBlockStore_FlushBlockProposal_Call struct { + *mock.Call +} + +// FlushBlockProposal is a helper method to define mock.On call +func (_e *EVMBlockStore_Expecter) FlushBlockProposal() *EVMBlockStore_FlushBlockProposal_Call { + return &EVMBlockStore_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} +} + +func (_c *EVMBlockStore_FlushBlockProposal_Call) Run(run func()) *EVMBlockStore_FlushBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EVMBlockStore_FlushBlockProposal_Call) Return(err error) *EVMBlockStore_FlushBlockProposal_Call { + _c.Call.Return(err) + return _c +} + +func (_c *EVMBlockStore_FlushBlockProposal_Call) RunAndReturn(run func() error) *EVMBlockStore_FlushBlockProposal_Call { + _c.Call.Return(run) + return _c +} + +// LatestBlock provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) LatestBlock() (*types.Block, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for LatestBlock") + } + + var r0 *types.Block + var r1 error + if returnFunc, ok := ret.Get(0).(func() (*types.Block, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() *types.Block); ok { + r0 = returnFunc() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*types.Block) + } + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// EVMBlockStore_LatestBlock_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'LatestBlock' +type EVMBlockStore_LatestBlock_Call struct { + *mock.Call +} + +// LatestBlock is a helper method to define mock.On call +func (_e *EVMBlockStore_Expecter) LatestBlock() *EVMBlockStore_LatestBlock_Call { + return &EVMBlockStore_LatestBlock_Call{Call: _e.mock.On("LatestBlock")} +} + +func (_c *EVMBlockStore_LatestBlock_Call) Run(run func()) *EVMBlockStore_LatestBlock_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EVMBlockStore_LatestBlock_Call) Return(block *types.Block, err error) *EVMBlockStore_LatestBlock_Call { + _c.Call.Return(block, err) + return _c +} + +func (_c *EVMBlockStore_LatestBlock_Call) RunAndReturn(run func() (*types.Block, error)) *EVMBlockStore_LatestBlock_Call { + _c.Call.Return(run) + return _c +} + +// ResetBlockProposal provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) ResetBlockProposal() { + _mock.Called() + return +} + +// EVMBlockStore_ResetBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBlockProposal' +type EVMBlockStore_ResetBlockProposal_Call struct { + *mock.Call +} + +// ResetBlockProposal is a helper method to define mock.On call +func (_e *EVMBlockStore_Expecter) ResetBlockProposal() *EVMBlockStore_ResetBlockProposal_Call { + return &EVMBlockStore_ResetBlockProposal_Call{Call: _e.mock.On("ResetBlockProposal")} +} + +func (_c *EVMBlockStore_ResetBlockProposal_Call) Run(run func()) *EVMBlockStore_ResetBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *EVMBlockStore_ResetBlockProposal_Call) Return() *EVMBlockStore_ResetBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *EVMBlockStore_ResetBlockProposal_Call) RunAndReturn(run func()) *EVMBlockStore_ResetBlockProposal_Call { + _c.Run(run) + return _c +} + +// StageBlockProposal provides a mock function for the type EVMBlockStore +func (_mock *EVMBlockStore) StageBlockProposal(blockProposal *types.BlockProposal) { + _mock.Called(blockProposal) + return +} + +// EVMBlockStore_StageBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StageBlockProposal' +type EVMBlockStore_StageBlockProposal_Call struct { + *mock.Call +} + +// StageBlockProposal is a helper method to define mock.On call +// - blockProposal *types.BlockProposal +func (_e *EVMBlockStore_Expecter) StageBlockProposal(blockProposal interface{}) *EVMBlockStore_StageBlockProposal_Call { + return &EVMBlockStore_StageBlockProposal_Call{Call: _e.mock.On("StageBlockProposal", blockProposal)} +} + +func (_c *EVMBlockStore_StageBlockProposal_Call) Run(run func(blockProposal *types.BlockProposal)) *EVMBlockStore_StageBlockProposal_Call { + _c.Call.Run(func(args mock.Arguments) { + var arg0 *types.BlockProposal + if args[0] != nil { + arg0 = args[0].(*types.BlockProposal) + } + run( + arg0, + ) + }) + return _c +} + +func (_c *EVMBlockStore_StageBlockProposal_Call) Return() *EVMBlockStore_StageBlockProposal_Call { + _c.Call.Return() + return _c +} + +func (_c *EVMBlockStore_StageBlockProposal_Call) RunAndReturn(run func(blockProposal *types.BlockProposal)) *EVMBlockStore_StageBlockProposal_Call { + _c.Run(run) + return _c +} diff --git a/fvm/environment/mock/reusable_cadence_runtime_interface.go b/fvm/environment/mock/reusable_cadence_runtime_interface.go deleted file mode 100644 index a1de2d9c6d4..00000000000 --- a/fvm/environment/mock/reusable_cadence_runtime_interface.go +++ /dev/null @@ -1,190 +0,0 @@ -// Code generated by mockery. DO NOT EDIT. - -package mock - -import ( - cadence "github.com/onflow/cadence" - common "github.com/onflow/cadence/common" - - environment "github.com/onflow/flow-go/fvm/environment" - - mock "github.com/stretchr/testify/mock" - - runtime "github.com/onflow/cadence/runtime" - - sema "github.com/onflow/cadence/sema" -) - -// ReusableCadenceRuntimeInterface is an autogenerated mock type for the ReusableCadenceRuntimeInterface type -type ReusableCadenceRuntimeInterface struct { - mock.Mock -} - -// CadenceScriptEnv provides a mock function with no fields -func (_m *ReusableCadenceRuntimeInterface) CadenceScriptEnv() runtime.Environment { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CadenceScriptEnv") - } - - var r0 runtime.Environment - if rf, ok := ret.Get(0).(func() runtime.Environment); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(runtime.Environment) - } - } - - return r0 -} - -// CadenceTXEnv provides a mock function with no fields -func (_m *ReusableCadenceRuntimeInterface) CadenceTXEnv() runtime.Environment { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for CadenceTXEnv") - } - - var r0 runtime.Environment - if rf, ok := ret.Get(0).(func() runtime.Environment); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(runtime.Environment) - } - } - - return r0 -} - -// ExecuteScript provides a mock function with given fields: script, location -func (_m *ReusableCadenceRuntimeInterface) ExecuteScript(script runtime.Script, location common.Location) (cadence.Value, error) { - ret := _m.Called(script, location) - - if len(ret) == 0 { - panic("no return value specified for ExecuteScript") - } - - var r0 cadence.Value - var r1 error - if rf, ok := ret.Get(0).(func(runtime.Script, common.Location) (cadence.Value, error)); ok { - return rf(script, location) - } - if rf, ok := ret.Get(0).(func(runtime.Script, common.Location) cadence.Value); ok { - r0 = rf(script, location) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cadence.Value) - } - } - - if rf, ok := ret.Get(1).(func(runtime.Script, common.Location) error); ok { - r1 = rf(script, location) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// InvokeContractFunction provides a mock function with given fields: contractLocation, functionName, arguments, argumentTypes -func (_m *ReusableCadenceRuntimeInterface) InvokeContractFunction(contractLocation common.AddressLocation, functionName string, arguments []cadence.Value, argumentTypes []sema.Type) (cadence.Value, error) { - ret := _m.Called(contractLocation, functionName, arguments, argumentTypes) - - if len(ret) == 0 { - panic("no return value specified for InvokeContractFunction") - } - - var r0 cadence.Value - var r1 error - if rf, ok := ret.Get(0).(func(common.AddressLocation, string, []cadence.Value, []sema.Type) (cadence.Value, error)); ok { - return rf(contractLocation, functionName, arguments, argumentTypes) - } - if rf, ok := ret.Get(0).(func(common.AddressLocation, string, []cadence.Value, []sema.Type) cadence.Value); ok { - r0 = rf(contractLocation, functionName, arguments, argumentTypes) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cadence.Value) - } - } - - if rf, ok := ret.Get(1).(func(common.AddressLocation, string, []cadence.Value, []sema.Type) error); ok { - r1 = rf(contractLocation, functionName, arguments, argumentTypes) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// NewTransactionExecutor provides a mock function with given fields: script, location -func (_m *ReusableCadenceRuntimeInterface) NewTransactionExecutor(script runtime.Script, location common.Location) runtime.Executor { - ret := _m.Called(script, location) - - if len(ret) == 0 { - panic("no return value specified for NewTransactionExecutor") - } - - var r0 runtime.Executor - if rf, ok := ret.Get(0).(func(runtime.Script, common.Location) runtime.Executor); ok { - r0 = rf(script, location) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(runtime.Executor) - } - } - - return r0 -} - -// ReadStored provides a mock function with given fields: address, path -func (_m *ReusableCadenceRuntimeInterface) ReadStored(address common.Address, path cadence.Path) (cadence.Value, error) { - ret := _m.Called(address, path) - - if len(ret) == 0 { - panic("no return value specified for ReadStored") - } - - var r0 cadence.Value - var r1 error - if rf, ok := ret.Get(0).(func(common.Address, cadence.Path) (cadence.Value, error)); ok { - return rf(address, path) - } - if rf, ok := ret.Get(0).(func(common.Address, cadence.Path) cadence.Value); ok { - r0 = rf(address, path) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(cadence.Value) - } - } - - if rf, ok := ret.Get(1).(func(common.Address, cadence.Path) error); ok { - r1 = rf(address, path) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// SetFvmEnvironment provides a mock function with given fields: fvmEnv -func (_m *ReusableCadenceRuntimeInterface) SetFvmEnvironment(fvmEnv environment.Environment) { - _m.Called(fvmEnv) -} - -// NewReusableCadenceRuntimeInterface creates a new instance of ReusableCadenceRuntimeInterface. 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 NewReusableCadenceRuntimeInterface(t interface { - mock.TestingT - Cleanup(func()) -}) *ReusableCadenceRuntimeInterface { - mock := &ReusableCadenceRuntimeInterface{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/fvm/evm/types/backend.go b/fvm/evm/backends/backend.go similarity index 93% rename from fvm/evm/types/backend.go rename to fvm/evm/backends/backend.go index ec0e7d188eb..cc538ea3d6e 100644 --- a/fvm/evm/types/backend.go +++ b/fvm/evm/backends/backend.go @@ -1,4 +1,4 @@ -package types +package backends import ( "github.com/onflow/flow-go/fvm/environment" @@ -14,7 +14,6 @@ type BackendStorage interface { // a `BackendError`. type Backend interface { BackendStorage - environment.BlockProposalCache environment.Meter environment.EventEmitter environment.BlockInfo @@ -24,5 +23,6 @@ type Backend interface { environment.Tracer environment.EVMMetricsReporter environment.LoggerProvider + environment.EVMBlockStore EVMTestOperationsAllowed() bool } diff --git a/fvm/evm/backends/wrappedEnv.go b/fvm/evm/backends/wrappedEnv.go index 5bf4567446a..809db1414db 100644 --- a/fvm/evm/backends/wrappedEnv.go +++ b/fvm/evm/backends/wrappedEnv.go @@ -1,6 +1,7 @@ package backends import ( + gocommon "github.com/ethereum/go-ethereum/common" "github.com/onflow/atree" "github.com/onflow/cadence" "github.com/onflow/cadence/common" @@ -25,10 +26,10 @@ type WrappedEnvironment struct { // NewWrappedEnvironment constructs a new wrapped environment func NewWrappedEnvironment(env environment.Environment) *WrappedEnvironment { - return &WrappedEnvironment{env: env} + return &WrappedEnvironment{env} } -var _ types.Backend = &WrappedEnvironment{} +var _ Backend = &WrappedEnvironment{} // GetValue gets a value from the storage for the given owner and key pair, // if value not found empty slice and no error is returned. @@ -137,26 +138,6 @@ func (we *WrappedEnvironment) Reset() { we.env.Reset() } -// CachedBlockProposal returns the currently cached block proposal, or nil if none is cached. -func (we *WrappedEnvironment) CachedBlockProposal() any { - return we.env.CachedBlockProposal() -} - -// CacheBlockProposal stores the given block proposal in the in-memory cache. -func (we *WrappedEnvironment) CacheBlockProposal(v any) { - we.env.CacheBlockProposal(v) -} - -// SetBlockProposalFlusher registers a function to persist the cached proposal to storage. -func (we *WrappedEnvironment) SetBlockProposalFlusher(f func() error) { - we.env.SetBlockProposalFlusher(f) -} - -// FlushBlockProposal calls the registered flusher to persist the cached proposal. -func (we *WrappedEnvironment) FlushBlockProposal() error { - return we.env.FlushBlockProposal() -} - // GetCurrentBlockHeight returns the current Flow block height func (we *WrappedEnvironment) GetCurrentBlockHeight() (uint64, error) { val, err := we.env.GetCurrentBlockHeight() @@ -245,3 +226,43 @@ func handleEnvironmentError(err error) error { return types.NewBackendError(err) } + +// BlockHash implements [Backend]. +func (we *WrappedEnvironment) BlockHash(height uint64) (gocommon.Hash, error) { + hash, err := we.env.BlockHash(height) + return hash, handleEnvironmentError(err) +} + +// BlockProposal implements [Backend]. +func (we *WrappedEnvironment) BlockProposal() (*types.BlockProposal, error) { + bp, err := we.env.BlockProposal() + return bp, handleEnvironmentError(err) +} + +// CommitBlockProposal implements [Backend]. +func (we *WrappedEnvironment) CommitBlockProposal(bp *types.BlockProposal) error { + err := we.env.CommitBlockProposal(bp) + return handleEnvironmentError(err) +} + +// LatestBlock implements [Backend]. +func (we *WrappedEnvironment) LatestBlock() (*types.Block, error) { + block, err := we.env.LatestBlock() + return block, handleEnvironmentError(err) +} + +// StageBlockProposal implements [Backend]. +func (we *WrappedEnvironment) StageBlockProposal(bp *types.BlockProposal) { + we.env.StageBlockProposal(bp) +} + +// FlushBlockProposal implements [Backend]. +func (we *WrappedEnvironment) FlushBlockProposal() error { + err := we.env.FlushBlockProposal() + return handleEnvironmentError(err) +} + +// ResetBlockProposal implements [Backend]. +func (we *WrappedEnvironment) ResetBlockProposal() { + we.env.ResetBlockProposal() +} diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 6edc34abfe1..3e746e57a85 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -170,7 +170,7 @@ func TestEVMRun(t *testing.T) { assert(res.status == EVM.Status.successful, message: "unexpected status") assert(res.errorCode == 0, message: "unexpected error code") - + return res } `, @@ -1465,7 +1465,7 @@ func TestEVMBatchRun(t *testing.T) { prepare(account: &Account) { let coinbase = EVM.EVMAddress(bytes: coinbaseBytes) let batchResults = EVM.batchRun(txs: txs, coinbase: coinbase) - + assert(batchResults.length == txs.length, message: "invalid result length") for res in batchResults { assert(res.status == EVM.Status.successful, message: "unexpected status") @@ -1657,7 +1657,7 @@ func TestEVMBatchRun(t *testing.T) { prepare(account: &Account) { let coinbase = EVM.EVMAddress(bytes: coinbaseBytes) let batchResults = EVM.batchRun(txs: txs, coinbase: coinbase) - + assert(batchResults.length == txs.length, message: "invalid result length") for i, res in batchResults { if i != %d { @@ -2826,7 +2826,7 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) { ` import EVM from %s import FlowToken from %s - + access(all) fun main(code: [UInt8]): EVM.Result { let admin = getAuthAccount(%s) @@ -2834,10 +2834,10 @@ func TestCadenceOwnedAccountFunctionalities(t *testing.T) { let minter <- admin.createNewMinter(allowedAmount: 2.34) let vault <- minter.mintTokens(amount: 2.34) destroy minter - + let cadenceOwnedAccount <- EVM.createCadenceOwnedAccount() cadenceOwnedAccount.deposit(from: <-vault) - + let res = cadenceOwnedAccount.deploy( code: code, gasLimit: 2_000_000, @@ -3324,7 +3324,7 @@ func TestDryRun(t *testing.T) { access(all) fun main(tx: [UInt8]): EVM.Result { return EVM.dryRun( - tx: tx, + tx: tx, from: EVM.EVMAddress(bytes: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]) ) }`, @@ -4015,7 +4015,7 @@ func TestDryRun(t *testing.T) { require.NoError(t, err) require.NoError(t, output.Err) - assert.Equal(t, uint64(86), output.ComputationUsed) + assert.Equal(t, uint64(85), output.ComputationUsed) }, ) }) @@ -6698,7 +6698,7 @@ func createAndFundFlowAccount( code := []byte(fmt.Sprintf( ` import FlowToken from %s - import FungibleToken from %s + import FungibleToken from %s transaction { prepare(account: auth(BorrowValue) &Account) { @@ -6768,7 +6768,7 @@ func setupCOA( transaction(amount: UFix64) { prepare(account: auth(Capabilities, Storage) &Account) { let cadenceOwnedAccount1 <- EVM.createCadenceOwnedAccount() - + let vaultRef = account.storage .borrow(from: /storage/flowTokenVault) ?? panic("Could not borrow reference to the owner's Vault!") @@ -6777,7 +6777,7 @@ func setupCOA( let vault <- vaultRef.withdraw(amount: amount) as! @FlowToken.Vault cadenceOwnedAccount1.deposit(from: <-vault) } - + account.storage.save<@EVM.CadenceOwnedAccount>( <-cadenceOwnedAccount1, to: /storage/coa diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 335c4a7b56f..8493509fd62 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -12,6 +12,7 @@ import ( "github.com/onflow/flow-go/fvm/environment" fvmErrors "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/emulator/state" "github.com/onflow/flow-go/fvm/evm/events" "github.com/onflow/flow-go/fvm/evm/handler/coa" @@ -26,9 +27,8 @@ type ContractHandler struct { flowChainID flow.ChainID evmContractAddress flow.Address flowTokenAddress common.Address - blockStore types.BlockStore addressAllocator types.AddressAllocator - backend types.Backend + backend backends.Backend emulator types.Emulator precompiledContracts []types.PrecompiledContract } @@ -41,16 +41,14 @@ func NewContractHandler( evmContractAddress flow.Address, flowTokenAddress common.Address, randomBeaconAddress flow.Address, - blockStore types.BlockStore, addressAllocator types.AddressAllocator, - backend types.Backend, + backend backends.Backend, emulator types.Emulator, ) *ContractHandler { return &ContractHandler{ flowChainID: flowChainID, evmContractAddress: evmContractAddress, flowTokenAddress: flowTokenAddress, - blockStore: blockStore, addressAllocator: addressAllocator, backend: backend, emulator: emulator, @@ -186,7 +184,7 @@ func (h *ContractHandler) AccountByAddress(addr types.Address, isAuthorized bool // LastExecutedBlock returns the last executed block func (h *ContractHandler) LastExecutedBlock() *types.Block { - block, err := h.blockStore.LatestBlock() + block, err := h.backend.LatestBlock() panicOnError(err) return block } @@ -372,7 +370,7 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte) ([]*types.Result, err } // update the block proposal - h.blockStore.StageBlockProposal(bp) + h.backend.StageBlockProposal(bp) return res, nil } @@ -385,13 +383,13 @@ func (h *ContractHandler) CommitBlockProposal() { func (h *ContractHandler) commitBlockProposal() error { // load latest block proposal - bp, err := h.blockStore.BlockProposal() + bp, err := h.backend.BlockProposal() if err != nil { return err } // commit the proposal - err = h.blockStore.CommitBlockProposal(bp) + err = h.backend.CommitBlockProposal(bp) if err != nil { return err } @@ -478,7 +476,7 @@ func (h *ContractHandler) run(rlpEncodedTx []byte) (*types.Result, error) { // step 8 - update the block proposal bp.AppendTransaction(res) - h.blockStore.StageBlockProposal(bp) + h.backend.StageBlockProposal(bp) // step 9 - emit transaction event err = h.emitEvent( @@ -662,7 +660,7 @@ func (h *ContractHandler) getBlockContext(bp *types.BlockProposal) ( BlockTimestamp: bp.Timestamp, DirectCallBaseGasUsage: types.DefaultDirectCallBaseGasUsage, GetHashFunc: func(n uint64) gethCommon.Hash { - hash, err := h.blockStore.BlockHash(n) + hash, err := h.backend.BlockHash(n) panicOnError(err) // we have to handle it here given we can't continue with it even in try case return hash }, @@ -675,7 +673,7 @@ func (h *ContractHandler) getBlockContext(bp *types.BlockProposal) ( } func (h *ContractHandler) getBlockProposal() (*types.BlockProposal, error) { - return h.blockStore.BlockProposal() + return h.backend.BlockProposal() } func (h *ContractHandler) executeAndHandleCall( @@ -747,7 +745,7 @@ func (h *ContractHandler) executeAndHandleCall( } // update the block proposal - h.blockStore.StageBlockProposal(bp) + h.backend.StageBlockProposal(bp) // step 8 - emit transaction event encoded, err := call.Encode() diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 935dabd4a2b..b2e46669b4d 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -17,6 +17,7 @@ import ( "github.com/onflow/flow-go/fvm/errors" "github.com/onflow/flow-go/fvm/evm" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/emulator" "github.com/onflow/flow-go/fvm/evm/emulator/state" "github.com/onflow/flow-go/fvm/evm/handler" @@ -46,8 +47,6 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { sc := systemcontracts.SystemContractsForChain(flow.Emulator) - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) - aa := handler.NewAddressAllocator() result := &types.Result{ @@ -68,7 +67,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -128,7 +127,6 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) aa := handler.NewAddressAllocator() em := &testutils.TestEmulator{ RunTransactionFunc: func(tx *gethTypes.Transaction) (*types.Result, error) { @@ -141,7 +139,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -191,7 +189,6 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) aa := handler.NewAddressAllocator() em := &testutils.TestEmulator{ RunTransactionFunc: func(tx *gethTypes.Transaction) (*types.Result, error) { @@ -202,7 +199,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) assertPanic(t, errors.IsFailure, func() { tx := eoa.PrepareSignAndEncodeTx( t, @@ -463,7 +460,6 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) aa := handler.NewAddressAllocator() // Withdraw calls are only possible within FOA accounts @@ -474,7 +470,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), false) account.Withdraw(types.NewBalanceFromUFix64(1)) @@ -491,7 +487,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.NewBalanceFromUFix64(1)) @@ -508,7 +504,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.NewBalanceFromUFix64(0)) @@ -525,7 +521,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Withdraw(types.NewBalanceFromUFix64(0)) @@ -541,7 +537,6 @@ func TestHandler_COA(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) aa := handler.NewAddressAllocator() // test non fatal error of emulator @@ -555,7 +550,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1))) @@ -572,7 +567,7 @@ func TestHandler_COA(t *testing.T) { }, } - handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Testnet, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) account := handler.AccountByAddress(testutils.RandomAddress(t), true) account.Deposit(types.NewFlowTokenVault(types.NewBalanceFromUFix64(1))) @@ -730,7 +725,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(chainID, backend, rootAddr) aa := handler.NewAddressAllocator() result := &types.Result{ @@ -772,7 +766,7 @@ func TestHandler_TransactionRun(t *testing.T) { }, nil }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) tx := eoa.PrepareSignAndEncodeTx( t, gethCommon.Address{}, @@ -800,7 +794,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(chainID, backend, rootAddr) aa := handler.NewAddressAllocator() result := &types.Result{ @@ -821,7 +814,7 @@ func TestHandler_TransactionRun(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) tx := eoa.PrepareSignAndEncodeTx( t, @@ -848,7 +841,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(chainID, backend, rootAddr) aa := handler.NewAddressAllocator() evmErr := fmt.Errorf("%w: next nonce %v, tx nonce %v", gethCore.ErrNonceTooLow, 1, 0) em := &testutils.TestEmulator{ @@ -859,7 +851,7 @@ func TestHandler_TransactionRun(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) @@ -903,7 +895,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { sc := systemcontracts.SystemContractsForChain(chainID) - bs := handler.NewBlockStore(chainID, backend, rootAddr) aa := handler.NewAddressAllocator() gasConsumed := testutils.RandomGas(1000) @@ -956,7 +947,7 @@ func TestHandler_TransactionRun(t *testing.T) { }, nil }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, aa, backend, em) gasLimit := uint64(100_000) @@ -1030,7 +1021,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(chainID, backend, rootAddr) aa := handler.NewAddressAllocator() gasConsumed := testutils.RandomGas(1000) @@ -1060,7 +1050,7 @@ func TestHandler_TransactionRun(t *testing.T) { return new(big.Int), nil }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, aa, backend, em) coinbase := types.NewAddress(gethCommon.Address{}) // batch run empty transactions @@ -1081,7 +1071,6 @@ func TestHandler_TransactionRun(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(defaultChainID, backend, rootAddr) aa := handler.NewAddressAllocator() nonce := uint64(1) @@ -1128,7 +1117,7 @@ func TestHandler_TransactionRun(t *testing.T) { }, } - handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, bs, aa, backend, em) + handler := handler.NewContractHandler(chainID, rootAddr, flowTokenAddress, randomBeaconAddress, aa, backend, em) rs := handler.DryRun(rlpTx, from) require.Equal(t, types.StatusSuccessful, rs.Status) @@ -1226,7 +1215,6 @@ func TestHandler_Metrics(t *testing.T) { rootAddr, flowTokenAddress, rootAddr, - handler.NewBlockStore(defaultChainID, backend, rootAddr), handler.NewAddressAllocator(), backend, em, @@ -1299,11 +1287,10 @@ func TestHandler_GetState(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) address := types.NewAddressFromString("0x7e093BA1474b79481f9B87D66c99a819F25e82E2") slot := gethCommon.HexToHash("0x5591cf37b43a6cc1c6a0b89114c8779fca21c866d7fc4a827ce040428eb28b78") @@ -1323,11 +1310,10 @@ func TestHandler_GetState_Disabled(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) address := types.NewAddressFromString("0x7e093BA1474b79481f9B87D66c99a819F25e82E2") slot := gethCommon.HexToHash("0x5591cf37b43a6cc1c6a0b89114c8779fca21c866d7fc4a827ce040428eb28b78") @@ -1349,7 +1335,6 @@ func TestHandler_SetState(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() execState, err := state.NewStateDB(backend, evm.StorageAccountAddress(flow.Emulator)) @@ -1366,7 +1351,7 @@ func TestHandler_SetState(t *testing.T) { require.Equal(t, gethCommon.Hash{}, prevValue) em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) handler.SetState(address, slot, value) }) @@ -1383,7 +1368,6 @@ func TestHandler_SetState_Disabled(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() execState, err := state.NewStateDB(backend, evm.StorageAccountAddress(flow.Emulator)) @@ -1400,7 +1384,7 @@ func TestHandler_SetState_Disabled(t *testing.T) { require.Equal(t, gethCommon.Hash{}, prevValue) em := &testutils.TestEmulator{} - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) assertPanic(t, types.IsAUnsupportedOperationError, func() { handler.SetState(address, slot, value) @@ -1419,7 +1403,6 @@ func TestHandler_RunTxAs(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() result := &types.Result{ ReturnedData: testutils.RandomData(t), @@ -1438,7 +1421,7 @@ func TestHandler_RunTxAs(t *testing.T) { return result, nil }, } - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) from := types.NewAddressFromString("0x7e093BA1474b79481f9B87D66c99a819F25e82E2") to := types.NewAddressFromString("0x7A038Ec292505B94d20004d3761Db0d1623bb45b") @@ -1461,7 +1444,6 @@ func TestHandler_RunTxAs_Disabled(t *testing.T) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { - bs := handler.NewBlockStore(flow.Emulator, backend, rootAddr) aa := handler.NewAddressAllocator() result := &types.Result{ ReturnedData: testutils.RandomData(t), @@ -1480,7 +1462,7 @@ func TestHandler_RunTxAs_Disabled(t *testing.T) { return result, nil }, } - handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, bs, aa, backend, em) + handler := handler.NewContractHandler(flow.Emulator, rootAddr, flowTokenAddress, rootAddr, aa, backend, em) from := types.NewAddressFromString("0x7e093BA1474b79481f9B87D66c99a819F25e82E2") to := types.NewAddressFromString("0x7A038Ec292505B94d20004d3761Db0d1623bb45b") @@ -1518,13 +1500,12 @@ func assertPanic(t *testing.T, check checkError, f func()) { f() } -func SetupHandler(t testing.TB, backend types.Backend, rootAddr flow.Address) *handler.ContractHandler { +func SetupHandler(t testing.TB, backend backends.Backend, rootAddr flow.Address) *handler.ContractHandler { return handler.NewContractHandler( flow.Emulator, rootAddr, flowTokenAddress, rootAddr, - handler.NewBlockStore(defaultChainID, backend, rootAddr), handler.NewAddressAllocator(), backend, emulator.NewEmulator(backend, rootAddr), diff --git a/fvm/evm/handler/precompiles.go b/fvm/evm/handler/precompiles.go index 10cd3d95701..ae45208e5c5 100644 --- a/fvm/evm/handler/precompiles.go +++ b/fvm/evm/handler/precompiles.go @@ -8,6 +8,7 @@ import ( "github.com/onflow/cadence/sema" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/precompiles" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" @@ -33,7 +34,7 @@ func preparePrecompiledContracts( evmContractAddress flow.Address, randomBeaconAddress flow.Address, addressAllocator types.AddressAllocator, - backend types.Backend, + backend backends.Backend, ) []types.PrecompiledContract { archAddress := addressAllocator.AllocatePrecompileAddress(1) archContract := precompiles.ArchContract( @@ -46,7 +47,7 @@ func preparePrecompiledContracts( return []types.PrecompiledContract{archContract} } -func blockHeightProvider(backend types.Backend) func() (uint64, error) { +func blockHeightProvider(backend backends.Backend) func() (uint64, error) { return func() (uint64, error) { h, err := backend.GetCurrentBlockHeight() if types.IsAFatalError(err) { @@ -58,7 +59,7 @@ func blockHeightProvider(backend types.Backend) func() (uint64, error) { const RandomSourceTypeValueFieldName = "value" -func randomSourceProvider(contractAddress flow.Address, backend types.Backend) func(uint64) ([]byte, error) { +func randomSourceProvider(contractAddress flow.Address, backend backends.Backend) func(uint64) ([]byte, error) { return func(blockHeight uint64) ([]byte, error) { value, err := backend.Invoke( environment.ContractFunctionSpec{ @@ -97,7 +98,7 @@ func randomSourceProvider(contractAddress flow.Address, backend types.Backend) f } } -func revertibleRandomGenerator(backend types.Backend) func() (uint64, error) { +func revertibleRandomGenerator(backend backends.Backend) func() (uint64, error) { return func() (uint64, error) { rand := make([]byte, 8) err := backend.ReadRandom(rand) @@ -111,7 +112,7 @@ func revertibleRandomGenerator(backend types.Backend) func() (uint64, error) { const ValidationResultTypeIsValidFieldName = "isValid" -func coaOwnershipProofValidator(contractAddress flow.Address, backend types.Backend) func(proof *types.COAOwnershipProofInContext) (bool, error) { +func coaOwnershipProofValidator(contractAddress flow.Address, backend backends.Backend) func(proof *types.COAOwnershipProofInContext) (bool, error) { return func(proof *types.COAOwnershipProofInContext) (bool, error) { value, err := backend.Invoke( environment.ContractFunctionSpec{ diff --git a/fvm/evm/offchain/blocks/blocks.go b/fvm/evm/offchain/blocks/blocks.go index 455790b9135..781acd939bf 100644 --- a/fvm/evm/offchain/blocks/blocks.go +++ b/fvm/evm/offchain/blocks/blocks.go @@ -5,7 +5,8 @@ import ( gethCommon "github.com/ethereum/go-ethereum/common" - "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -16,9 +17,9 @@ const BlockStoreLatestBlockMetaKey = "LatestBlockMeta" // and also the latest executed block meta data type Blocks struct { chainID flow.ChainID - storage types.BackendStorage + storage backends.BackendStorage rootAddress flow.Address - bhl *handler.BlockHashList + bhl *environment.BlockHashList } var _ types.BlockSnapshot = (*Blocks)(nil) @@ -27,7 +28,7 @@ var _ types.BlockSnapshot = (*Blocks)(nil) func NewBlocks( chainID flow.ChainID, rootAddress flow.Address, - storage types.BackendStorage, + storage backends.BackendStorage, ) (*Blocks, error) { var err error blocks := &Blocks{ @@ -35,10 +36,10 @@ func NewBlocks( storage: storage, rootAddress: rootAddress, } - blocks.bhl, err = handler.NewBlockHashList( + blocks.bhl, err = environment.NewBlockHashList( storage, rootAddress, - handler.BlockHashListCapacity, + environment.BlockHashListCapacity, ) if err != nil { return nil, err diff --git a/fvm/evm/offchain/blocks/provider.go b/fvm/evm/offchain/blocks/provider.go index b9da39bd468..e14b566e6d3 100644 --- a/fvm/evm/offchain/blocks/provider.go +++ b/fvm/evm/offchain/blocks/provider.go @@ -3,8 +3,9 @@ package blocks import ( "fmt" + "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/events" - "github.com/onflow/flow-go/fvm/evm/handler" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -17,7 +18,7 @@ type BasicProvider struct { chainID flow.ChainID blks *Blocks rootAddr flow.Address - storage types.BackendStorage + storage backends.BackendStorage latestBlockPayload *events.BlockEventPayload } @@ -25,7 +26,7 @@ var _ types.BlockSnapshotProvider = (*BasicProvider)(nil) func NewBasicProvider( chainID flow.ChainID, - storage types.BackendStorage, + storage backends.BackendStorage, rootAddr flow.Address, ) (*BasicProvider, error) { blks, err := NewBlocks(chainID, rootAddr, storage) @@ -87,7 +88,7 @@ func (p *BasicProvider) OnBlockExecuted( // do the same as handler.CommitBlockProposal err = p.storage.SetValue( p.rootAddr[:], - []byte(handler.BlockStoreLatestBlockKey), + []byte(environment.BlockStoreLatestBlockKey), blockBytes, ) if err != nil { @@ -103,7 +104,7 @@ func (p *BasicProvider) OnBlockExecuted( // update block proposal err = p.storage.SetValue( p.rootAddr[:], - []byte(handler.BlockStoreLatestBlockProposalKey), + []byte(environment.BlockStoreLatestBlockProposalKey), blockProposalBytes, ) if err != nil { diff --git a/fvm/evm/offchain/storage/ephemeral.go b/fvm/evm/offchain/storage/ephemeral.go index a1687460526..be5eedc4866 100644 --- a/fvm/evm/offchain/storage/ephemeral.go +++ b/fvm/evm/offchain/storage/ephemeral.go @@ -6,6 +6,7 @@ import ( "github.com/onflow/atree" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/model/flow" ) @@ -14,19 +15,19 @@ import ( // the provided backend storage. It can be used for dry running transaction/calls // or batching updates for atomic operations. type EphemeralStorage struct { - parent types.BackendStorage + parent backends.BackendStorage deltas map[flow.RegisterID]flow.RegisterValue } // NewEphemeralStorage constructs a new EphemeralStorage -func NewEphemeralStorage(parent types.BackendStorage) *EphemeralStorage { +func NewEphemeralStorage(parent backends.BackendStorage) *EphemeralStorage { return &EphemeralStorage{ parent: parent, deltas: make(map[flow.RegisterID]flow.RegisterValue), } } -var _ types.BackendStorage = (*EphemeralStorage)(nil) +var _ backends.BackendStorage = (*EphemeralStorage)(nil) var _ types.ReplayResultCollector = (*EphemeralStorage)(nil) diff --git a/fvm/evm/offchain/storage/readonly.go b/fvm/evm/offchain/storage/readonly.go index 6c66e7c1e43..daecced2bcd 100644 --- a/fvm/evm/offchain/storage/readonly.go +++ b/fvm/evm/offchain/storage/readonly.go @@ -5,6 +5,7 @@ import ( "github.com/onflow/atree" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/types" ) @@ -13,7 +14,7 @@ type ReadOnlyStorage struct { snapshot types.BackendStorageSnapshot } -var _ types.BackendStorage = &ReadOnlyStorage{} +var _ backends.BackendStorage = &ReadOnlyStorage{} // NewReadOnlyStorage constructs a new ReadOnlyStorage using the given snapshot func NewReadOnlyStorage(snapshot types.BackendStorageSnapshot) *ReadOnlyStorage { diff --git a/fvm/evm/offchain/sync/replay.go b/fvm/evm/offchain/sync/replay.go index a9fe6b2f955..c83535362cf 100644 --- a/fvm/evm/offchain/sync/replay.go +++ b/fvm/evm/offchain/sync/replay.go @@ -9,6 +9,7 @@ import ( gethTrie "github.com/ethereum/go-ethereum/trie" "github.com/onflow/atree" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/emulator" "github.com/onflow/flow-go/fvm/evm/events" "github.com/onflow/flow-go/fvm/evm/precompiles" @@ -24,7 +25,7 @@ var emptyChecksum = [types.ChecksumLength]byte{0, 0, 0, 0} func ReplayBlockExecution( chainID flow.ChainID, rootAddr flow.Address, - storage types.BackendStorage, + storage backends.BackendStorage, blockSnapshot types.BlockSnapshot, tracer *gethTracer.Tracer, transactionEvents []events.TransactionEventPayload, diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index ffdda66084c..2c37f31b5de 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -6,6 +6,7 @@ import ( "fmt" "testing" + gocommon "github.com/ethereum/go-ethereum/common" "github.com/onflow/cadence/stdlib" "github.com/rs/zerolog" otelTrace "go.opentelemetry.io/otel/trace" @@ -38,16 +39,20 @@ func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(fl } func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { + vs := GetSimpleValueStore() + bi := getSimpleBlockInfo() + rg := getSimpleRandomGenerator() tb := &TestBackend{ - TestValueStore: GetSimpleValueStore(), + TestValueStore: vs, testEventEmitter: getSimpleEventEmitter(), testMeter: getSimpleMeter(), - TestBlockInfo: getSimpleBlockStore(), - TestRandomGenerator: getSimpleRandomGenerator(), + TestBlockInfo: bi, + TestRandomGenerator: rg, TestContractFunctionInvoker: &TestContractFunctionInvoker{}, TestTracer: &TestTracer{}, TestMetricsReporter: &TestMetricsReporter{}, TestLoggerProvider: &TestLoggerProvider{}, + TestBlockStore: getSimpleBlockStore(vs, bi, rg), } f(tb) } @@ -209,7 +214,7 @@ func getSimpleMeter() *testMeter { } } -func getSimpleBlockStore() *TestBlockInfo { +func getSimpleBlockInfo() *TestBlockInfo { var index int64 = 1 return &TestBlockInfo{ GetCurrentBlockHeightFunc: func() (uint64, error) { @@ -227,6 +232,34 @@ func getSimpleBlockStore() *TestBlockInfo { } } +func getSimpleBlockStore(vs *TestValueStore, bi *TestBlockInfo, rg *TestRandomGenerator) *TestBlockStore { + bs := environment.NewBlockStore(flow.Testnet, vs, bi, rg, TestFlowEVMRootAddress) + + return &TestBlockStore{ + LatestBlockFunc: func() (*types.Block, error) { + return bs.LatestBlock() + }, + BlockHashFunc: func(height uint64) (gocommon.Hash, error) { + return bs.BlockHash(height) + }, + BlockProposalFunc: func() (*types.BlockProposal, error) { + return bs.BlockProposal() + }, + StageBlockProposalFunc: func(_bp *types.BlockProposal) { + bs.StageBlockProposal(_bp) + }, + FlushBlockProposalFunc: func() error { + return bs.FlushBlockProposal() + }, + CommitBlockProposalFunc: func(_bp *types.BlockProposal) error { + return bs.CommitBlockProposal(_bp) + }, + ResetBlockProposalFunc: func() { + bs.ResetBlockProposal() + }, + } +} + type TestBackend struct { *TestValueStore *testMeter @@ -238,28 +271,14 @@ type TestBackend struct { *TestTracer *TestMetricsReporter *TestLoggerProvider + *TestBlockStore evmTestOperationsAllowed bool - cachedProposal any } func (tb *TestBackend) EVMTestOperationsAllowed() bool { return tb.evmTestOperationsAllowed } -func (tb *TestBackend) CachedBlockProposal() any { - return tb.cachedProposal -} - -func (tb *TestBackend) CacheBlockProposal(v any) { - tb.cachedProposal = v -} - -func (tb *TestBackend) SetBlockProposalFlusher(func() error) {} - -func (tb *TestBackend) FlushBlockProposal() error { return nil } - -var _ types.Backend = &TestBackend{} - func (tb *TestBackend) TotalStorageSize() int { if tb.TotalStorageSizeFunc == nil { panic("method not set") @@ -712,3 +731,71 @@ func (tlp *TestLoggerProvider) Logger() zerolog.Logger { } return zerolog.Nop() } + +type TestBlockStore struct { + BlockHashFunc func(height uint64) (gocommon.Hash, error) + BlockProposalFunc func() (*types.BlockProposal, error) + CommitBlockProposalFunc func(*types.BlockProposal) error + LatestBlockFunc func() (*types.Block, error) + StageBlockProposalFunc func(*types.BlockProposal) + FlushBlockProposalFunc func() error + ResetBlockProposalFunc func() +} + +var _ environment.EVMBlockStore = &TestBlockStore{} + +func (tb *TestBlockStore) BlockHash(height uint64) (gocommon.Hash, error) { + blockHashFunc := tb.BlockHashFunc + if blockHashFunc == nil { + panic("BlockHashFunc method is not set") + } + return blockHashFunc(height) +} + +func (tb *TestBlockStore) BlockProposal() (*types.BlockProposal, error) { + blockProposalFunc := tb.BlockProposalFunc + if blockProposalFunc == nil { + panic("BlockProposalFunc method is not set") + } + return blockProposalFunc() +} + +func (tb *TestBlockStore) CommitBlockProposal(bp *types.BlockProposal) error { + commitBlockProposalFunc := tb.CommitBlockProposalFunc + if commitBlockProposalFunc == nil { + panic("CommitBlockProposalFunc method is not set") + } + return commitBlockProposalFunc(bp) +} + +func (tb *TestBlockStore) LatestBlock() (*types.Block, error) { + latestBlockFunc := tb.LatestBlockFunc + if latestBlockFunc == nil { + panic("LatestBlockFunc method is not set") + } + return latestBlockFunc() +} + +func (tb *TestBlockStore) StageBlockProposal(bp *types.BlockProposal) { + stageBlockProposalFunc := tb.StageBlockProposalFunc + if stageBlockProposalFunc == nil { + panic("StageBlockProposalFunc method is not set") + } + stageBlockProposalFunc(bp) +} + +func (tb *TestBlockStore) FlushBlockProposal() error { + flushBlockProposalFunc := tb.FlushBlockProposalFunc + if flushBlockProposalFunc == nil { + panic("FlushBlockProposalFunc method is not set") + } + return flushBlockProposalFunc() +} + +func (tb *TestBlockStore) ResetBlockProposal() { + resetFunc := tb.ResetBlockProposalFunc + if resetFunc == nil { + panic("ResetBlockProposalFunc method is not set") + } + resetFunc() +} diff --git a/fvm/evm/testutils/handler.go b/fvm/evm/testutils/handler.go index 2b1301e1f0e..d266bb57e49 100644 --- a/fvm/evm/testutils/handler.go +++ b/fvm/evm/testutils/handler.go @@ -3,6 +3,7 @@ package testutils import ( "github.com/onflow/cadence/common" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/emulator" "github.com/onflow/flow-go/fvm/evm/handler" "github.com/onflow/flow-go/fvm/evm/precompiles" @@ -13,7 +14,7 @@ import ( func SetupHandler( chainID flow.ChainID, - backend types.Backend, + backend backends.Backend, rootAddr flow.Address, ) *handler.ContractHandler { return handler.NewContractHandler( @@ -21,7 +22,6 @@ func SetupHandler( rootAddr, common.Address(systemcontracts.SystemContractsForChain(chainID).FlowToken.Address), rootAddr, - handler.NewBlockStore(chainID, backend, rootAddr), handler.NewAddressAllocator(), backend, emulator.NewEmulator(backend, rootAddr), diff --git a/fvm/evm/testutils/offchain.go b/fvm/evm/testutils/offchain.go index 5a5e675798a..ecd2b97b00d 100644 --- a/fvm/evm/testutils/offchain.go +++ b/fvm/evm/testutils/offchain.go @@ -3,6 +3,7 @@ package testutils import ( "fmt" + "github.com/onflow/flow-go/fvm/evm/backends" "github.com/onflow/flow-go/fvm/evm/offchain/storage" "github.com/onflow/flow-go/fvm/evm/types" ) @@ -11,7 +12,7 @@ import ( // storage provider that only provides // storage for an specific height type TestStorageProvider struct { - storage types.BackendStorage + storage backends.BackendStorage height uint64 } diff --git a/fvm/evm/types/handler.go b/fvm/evm/types/handler.go index 62b1c06b5d5..b93aece4a70 100644 --- a/fvm/evm/types/handler.go +++ b/fvm/evm/types/handler.go @@ -90,23 +90,3 @@ type AddressAllocator interface { // AllocateAddress allocates an address by index to be used by a precompile contract AllocatePrecompileAddress(index uint64) Address } - -// BlockStore stores the chain of blocks -type BlockStore interface { - // LatestBlock returns the latest appended block - LatestBlock() (*Block, error) - - // BlockHash returns the hash of the block at the given height - BlockHash(height uint64) (gethCommon.Hash, error) - - // BlockProposal returns the active block proposal - BlockProposal() (*BlockProposal, error) - - // StageBlockProposal updates the in-memory block proposal cache without writing to storage. - // Persistence is handled automatically at the end of the Cadence transaction via - // the flusher registered on the backend's BlockProposalCache. - StageBlockProposal(*BlockProposal) - - // CommitBlockProposal commits the block proposal and update the chain of blocks - CommitBlockProposal(*BlockProposal) error -} diff --git a/fvm/fvm_test.go b/fvm/fvm_test.go index 3b192b84184..7acd2c76a3a 100644 --- a/fvm/fvm_test.go +++ b/fvm/fvm_test.go @@ -40,7 +40,6 @@ import ( 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" "github.com/onflow/flow-go/fvm/meter" @@ -4227,7 +4226,7 @@ func Test_BlockHashListShouldWriteOnPush(t *testing.T) { chain := flow.Emulator.Chain() sc := systemcontracts.SystemContractsForChain(chain.ChainID()) - push := func(bhl *handler.BlockHashList, height uint64) { + push := func(bhl *environment.BlockHashList, height uint64) { buffer := make([]byte, 32) pos := 0 @@ -4262,7 +4261,7 @@ func Test_BlockHashListShouldWriteOnPush(t *testing.T) { accounts, ) - bhl, err := handler.NewBlockHashList(valueStore, sc.EVMStorage.Address, capacity) + bhl, err := environment.NewBlockHashList(valueStore, sc.EVMStorage.Address, capacity) require.NoError(t, err) // fill the block hash list @@ -4287,7 +4286,7 @@ func Test_BlockHashListShouldWriteOnPush(t *testing.T) { accounts, ) - bhl, err = handler.NewBlockHashList(valueStore, sc.EVMStorage.Address, capacity) + bhl, err = environment.NewBlockHashList(valueStore, sc.EVMStorage.Address, capacity) require.NoError(t, err) // after we push the changes should be applied and the first block hash in the bucket should be capacity+1 instead of 0 diff --git a/fvm/runtime/cadence_function_declarations.go b/fvm/runtime/cadence_function_declarations.go index 49854fec040..d1b68a7a10b 100644 --- a/fvm/runtime/cadence_function_declarations.go +++ b/fvm/runtime/cadence_function_declarations.go @@ -93,7 +93,6 @@ func EVMInternalEVMContractValue(chainID flow.ChainID, fvmEnv environment.Enviro evmBackend := backends.NewWrappedEnvironment(fvmEnv) evmEmulator := emulator.NewEmulator(evmBackend, evm.StorageAccountAddress(chainID)) - blockStore := handler.NewBlockStore(chainID, evmBackend, evm.StorageAccountAddress(chainID)) addressAllocator := handler.NewAddressAllocator() evmContractAddress := evm.ContractAccountAddress(chainID) @@ -103,7 +102,6 @@ func EVMInternalEVMContractValue(chainID flow.ChainID, fvmEnv environment.Enviro evmContractAddress, common.Address(flowTokenAddress), randomBeaconAddress, - blockStore, addressAllocator, evmBackend, evmEmulator, From edf84f5217bc593d7b710a67d8d25bec674c1ef5 Mon Sep 17 00:00:00 2001 From: Patrick Fuchs Date: Thu, 2 Apr 2026 01:45:10 +0200 Subject: [PATCH 03/10] add chainID to TestBackend --- fvm/environment/evm_block_hash_list_test.go | 2 +- .../evm_block_store_benchmark_test.go | 3 +- fvm/environment/evm_block_store_test.go | 2 +- fvm/evm/emulator/emulator_test.go | 18 +++---- fvm/evm/evm_test.go | 4 +- fvm/evm/handler/handler_benchmark_test.go | 2 +- fvm/evm/handler/handler_test.go | 54 +++++++++---------- fvm/evm/offchain/query/view_test.go | 4 +- fvm/evm/offchain/sync/replayer_test.go | 2 +- fvm/evm/testutils/backend.go | 15 ++++-- 10 files changed, 56 insertions(+), 50 deletions(-) diff --git a/fvm/environment/evm_block_hash_list_test.go b/fvm/environment/evm_block_hash_list_test.go index 869594018e5..8e731ada078 100644 --- a/fvm/environment/evm_block_hash_list_test.go +++ b/fvm/environment/evm_block_hash_list_test.go @@ -12,7 +12,7 @@ import ( ) func TestBlockHashList(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { capacity := 256 bhl, err := environment.NewBlockHashList(backend, root, capacity) diff --git a/fvm/environment/evm_block_store_benchmark_test.go b/fvm/environment/evm_block_store_benchmark_test.go index 28c6ecfc55c..7f7e5170a21 100644 --- a/fvm/environment/evm_block_store_benchmark_test.go +++ b/fvm/environment/evm_block_store_benchmark_test.go @@ -14,7 +14,7 @@ import ( func BenchmarkProposalGrowth(b *testing.B) { benchmarkBlockProposalGrowth(b, 1000) } func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { - testutils.RunWithTestBackend(b, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(b, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(b, backend, func(rootAddr flow.Address) { bs := environment.NewBlockStore(flow.Testnet, backend, backend, backend, rootAddr) @@ -24,7 +24,6 @@ func benchmarkBlockProposalGrowth(b *testing.B, txCounts int) { res := testutils.RandomResultFixture(b) bp.AppendTransaction(res) bs.StageBlockProposal(bp) - require.NoError(b, err) } // check the impact of updating block proposal after x number of transactions diff --git a/fvm/environment/evm_block_store_test.go b/fvm/environment/evm_block_store_test.go index 72304fe3256..afeeb1210f4 100644 --- a/fvm/environment/evm_block_store_test.go +++ b/fvm/environment/evm_block_store_test.go @@ -16,7 +16,7 @@ import ( func TestBlockStore(t *testing.T) { var chainID = flow.Testnet - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, chainID, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { bs := environment.NewBlockStore(chainID, backend, backend, backend, root) diff --git a/fvm/evm/emulator/emulator_test.go b/fvm/evm/emulator/emulator_test.go index 9631c821b5d..78f3d73c65e 100644 --- a/fvm/evm/emulator/emulator_test.go +++ b/fvm/evm/emulator/emulator_test.go @@ -50,7 +50,7 @@ func requireSuccessfulExecution(t testing.TB, err error, res *types.Result) { } func TestNativeTokenBridging(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { originalBalance := types.MakeBigIntInFlow(3) testAccount := types.NewAddressFromString("test") @@ -245,7 +245,7 @@ func TestNativeTokenBridging(t *testing.T) { func TestContractInteraction(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testContract := testutils.GetStorageTestContract(t) @@ -635,7 +635,7 @@ func TestContractInteraction(t *testing.T) { } func TestDeployAtFunctionality(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testContract := testutils.GetStorageTestContract(t) testAccount := types.NewAddressFromString("test") @@ -724,7 +724,7 @@ func TestDeployAtFunctionality(t *testing.T) { // EIP 6780 https://eips.ethereum.org/EIPS/eip-6780 in case where the selfdestruct // is not called in the same transaction as deployment. func TestSelfdestruct(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *testutils.EOATestAccount) { @@ -808,7 +808,7 @@ func TestSelfdestruct(t *testing.T) { // test factory patterns func TestFactoryPatterns(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { var factoryAddress types.Address @@ -1067,7 +1067,7 @@ func TestFactoryPatterns(t *testing.T) { } func TestTransfers(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testAccount1 := types.NewAddressFromString("test1") @@ -1107,7 +1107,7 @@ func TestTransfers(t *testing.T) { } func TestStorageNoSideEffect(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { var err error em := emulator.NewEmulator(backend, flowEVMRoot) @@ -1131,7 +1131,7 @@ func TestStorageNoSideEffect(t *testing.T) { } func TestCallingExtraPrecompiles(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(flowEVMRoot flow.Address) { RunWithNewEmulator(t, backend, flowEVMRoot, func(em *emulator.Emulator) { @@ -1198,7 +1198,7 @@ func TestCallingExtraPrecompiles(t *testing.T) { } func TestTxIndex(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) { ctx := types.NewDefaultBlockContext(blockNumber.Uint64()) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index 3e746e57a85..a22c55f0263 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -6969,7 +6969,7 @@ func RunWithNewEnvironment( bootstrapOpts ...fvm.BootstrapProcedureOption, ) { rootAddr := evm.StorageAccountAddress(chain.ChainID()) - RunWithTestBackend(t, func(backend *TestBackend) { + RunWithTestBackend(t, chain.ChainID(), func(backend *TestBackend) { RunWithDeployedContract(t, GetStorageTestContract(t), backend, rootAddr, func(testContract *TestContract) { RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *EOATestAccount) { blocks := new(envMock.Blocks) @@ -7030,7 +7030,7 @@ func RunContractWithNewEnvironment( ) { rootAddr := evm.StorageAccountAddress(chain.ChainID()) - RunWithTestBackend(t, func(backend *TestBackend) { + RunWithTestBackend(t, chain.ChainID(), func(backend *TestBackend) { RunWithDeployedContract(t, tc, backend, rootAddr, func(testContract *TestContract) { RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *EOATestAccount) { diff --git a/fvm/evm/handler/handler_benchmark_test.go b/fvm/evm/handler/handler_benchmark_test.go index 136a431aec7..7fe9326014f 100644 --- a/fvm/evm/handler/handler_benchmark_test.go +++ b/fvm/evm/handler/handler_benchmark_test.go @@ -14,7 +14,7 @@ func BenchmarkStorage(b *testing.B) { benchmarkStorageGrowth(b, 100, 100, 100) } // benchmark func benchmarkStorageGrowth(b *testing.B, accountCount, setupKittyCount, txPerBlock int) { - testutils.RunWithTestBackend(b, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(b, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(b, backend, func(rootAddr flow.Address) { testutils.RunWithDeployedContract(b, testutils.GetDummyKittyTestContract(b), diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index b2e46669b4d..49435a2d9f7 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -41,7 +41,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { t.Run("test RunOrPanic run (happy case)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -124,7 +124,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { t.Run("test RunOrPanic (unhappy non-fatal cases)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -186,7 +186,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { t.Run("test RunOrPanic (fatal cases)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -220,7 +220,7 @@ func TestHandler_TransactionRunOrPanic(t *testing.T) { t.Run("test RunOrPanic (with integrated emulator)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) @@ -280,7 +280,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { t.Run("test last executed block call", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) @@ -305,7 +305,7 @@ func TestHandler_OpsWithoutEmulator(t *testing.T) { t.Run("test address allocation", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { h := SetupHandler(t, backend, rootAddr) @@ -323,7 +323,7 @@ func TestHandler_COA(t *testing.T) { t.Parallel() t.Run("test deposit/withdraw (with integrated emulator)", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { sc := systemcontracts.SystemContractsForChain(flow.Emulator) @@ -410,7 +410,7 @@ func TestHandler_COA(t *testing.T) { }) t.Run("test coa deployment", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { h := SetupHandler(t, backend, rootAddr) @@ -457,7 +457,7 @@ func TestHandler_COA(t *testing.T) { }) t.Run("test withdraw (unhappy case)", func(t *testing.T) { - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -534,7 +534,7 @@ func TestHandler_COA(t *testing.T) { t.Run("test deposit (unhappy case)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -581,7 +581,7 @@ func TestHandler_COA(t *testing.T) { t.Parallel() // TODO update this test with events, gas metering, etc - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { handler := SetupHandler(t, backend, rootAddr) @@ -623,7 +623,7 @@ func TestHandler_COA(t *testing.T) { t.Run("test call to cadence arch", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { blockHeight := uint64(123) backend.GetCurrentBlockHeightFunc = func() (uint64, error) { return blockHeight, nil @@ -677,7 +677,7 @@ func TestHandler_COA(t *testing.T) { t.Run("test block.random call (with integrated emulator)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { random := testutils.RandomCommonHash(t) backend.ReadRandomFunc = func(buffer []byte) error { copy(buffer, random.Bytes()) @@ -721,7 +721,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - transaction run (success)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -790,7 +790,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - transaction run (failed)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -838,7 +838,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - transaction run (unhappy cases)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -890,7 +890,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - transaction batch run (success)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { sc := systemcontracts.SystemContractsForChain(chainID) @@ -1018,7 +1018,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - transaction batch run (unhappy case)", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { aa := handler.NewAddressAllocator() @@ -1067,7 +1067,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test dry run successful", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -1132,7 +1132,7 @@ func TestHandler_TransactionRun(t *testing.T) { t.Run("test - open tracing", func(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { @@ -1184,7 +1184,7 @@ func TestHandler_TransactionRun(t *testing.T) { func TestHandler_Metrics(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { gasUsed := testutils.RandomGas(1000) @@ -1281,7 +1281,7 @@ func TestHandler_Metrics(t *testing.T) { func TestHandler_GetState(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(true) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -1304,7 +1304,7 @@ func TestHandler_GetState(t *testing.T) { func TestHandler_GetState_Disabled(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(false) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -1329,7 +1329,7 @@ func TestHandler_GetState_Disabled(t *testing.T) { func TestHandler_SetState(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(true) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -1362,7 +1362,7 @@ func TestHandler_SetState(t *testing.T) { func TestHandler_SetState_Disabled(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(false) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -1397,7 +1397,7 @@ func TestHandler_SetState_Disabled(t *testing.T) { func TestHandler_RunTxAs(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(true) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { @@ -1438,7 +1438,7 @@ func TestHandler_RunTxAs(t *testing.T) { func TestHandler_RunTxAs_Disabled(t *testing.T) { t.Parallel() - testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestBackend(t, flow.Testnet, func(backend *testutils.TestBackend) { backend.SetEVMTestOperationsAllowed(false) testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { diff --git a/fvm/evm/offchain/query/view_test.go b/fvm/evm/offchain/query/view_test.go index ec172f98cce..ba3438fe225 100644 --- a/fvm/evm/offchain/query/view_test.go +++ b/fvm/evm/offchain/query/view_test.go @@ -26,7 +26,7 @@ import ( func TestView(t *testing.T) { const chainID = flow.Emulator - RunWithTestBackend(t, func(backend *TestBackend) { + RunWithTestBackend(t, chainID, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { RunWithDeployedContract(t, GetStorageTestContract(t), backend, rootAddr, func(testContract *TestContract) { @@ -189,7 +189,7 @@ func TestView(t *testing.T) { func TestViewStateOverrides(t *testing.T) { const chainID = flow.Emulator - RunWithTestBackend(t, func(backend *TestBackend) { + RunWithTestBackend(t, chainID, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { RunWithDeployedContract(t, GetStorageTestContract(t), backend, rootAddr, func(testContract *TestContract) { diff --git a/fvm/evm/offchain/sync/replayer_test.go b/fvm/evm/offchain/sync/replayer_test.go index 6297a7e2531..d92a12bb644 100644 --- a/fvm/evm/offchain/sync/replayer_test.go +++ b/fvm/evm/offchain/sync/replayer_test.go @@ -23,7 +23,7 @@ func TestChainReplay(t *testing.T) { const chainID = flow.Emulator var snapshot *TestValueStore - RunWithTestBackend(t, func(backend *TestBackend) { + RunWithTestBackend(t, chainID, func(backend *TestBackend) { RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { RunWithDeployedContract(t, GetStorageTestContract(t), backend, rootAddr, func(testContract *TestContract) { diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 2c37f31b5de..52ea7055370 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -20,6 +20,7 @@ import ( "golang.org/x/exp/maps" "github.com/onflow/flow-go/fvm/environment" + "github.com/onflow/flow-go/fvm/evm" "github.com/onflow/flow-go/fvm/evm/types" "github.com/onflow/flow-go/fvm/meter" "github.com/onflow/flow-go/fvm/tracing" @@ -38,7 +39,7 @@ func RunWithTestFlowEVMRootAddress(t testing.TB, backend atree.Ledger, f func(fl f(TestFlowEVMRootAddress) } -func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { +func RunWithTestBackend(t testing.TB, chain flow.ChainID, f func(*TestBackend)) { vs := GetSimpleValueStore() bi := getSimpleBlockInfo() rg := getSimpleRandomGenerator() @@ -52,7 +53,7 @@ func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { TestTracer: &TestTracer{}, TestMetricsReporter: &TestMetricsReporter{}, TestLoggerProvider: &TestLoggerProvider{}, - TestBlockStore: getSimpleBlockStore(vs, bi, rg), + TestBlockStore: getSimpleBlockStore(chain, vs, bi, rg), } f(tb) } @@ -232,8 +233,14 @@ func getSimpleBlockInfo() *TestBlockInfo { } } -func getSimpleBlockStore(vs *TestValueStore, bi *TestBlockInfo, rg *TestRandomGenerator) *TestBlockStore { - bs := environment.NewBlockStore(flow.Testnet, vs, bi, rg, TestFlowEVMRootAddress) +func getSimpleBlockStore(chain flow.ChainID, vs *TestValueStore, bi *TestBlockInfo, rg *TestRandomGenerator) *TestBlockStore { + bs := environment.NewBlockStore( + chain, + vs, + bi, + rg, + evm.StorageAccountAddress(chain), + ) return &TestBlockStore{ LatestBlockFunc: func() (*types.Block, error) { From 5048bafd3f9f9336dc6162023c17abc5e686d04e Mon Sep 17 00:00:00 2001 From: Patrick Fuchs Date: Thu, 2 Apr 2026 02:11:46 +0200 Subject: [PATCH 04/10] fvm/evm: add regression test for dryRun followed by run in same transaction --- fvm/evm/evm_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/fvm/evm/evm_test.go b/fvm/evm/evm_test.go index a22c55f0263..8becebd2e48 100644 --- a/fvm/evm/evm_test.go +++ b/fvm/evm/evm_test.go @@ -4019,6 +4019,90 @@ func TestDryRun(t *testing.T) { }, ) }) + + // Regression test: dryRun reads the block proposal into the cache. A subsequent + // EVM.run in the same Cadence transaction must still see a clean proposal (not + // one tainted by the dry-run read) and complete successfully. + t.Run("test EVM.dryRun followed by EVM.run in same transaction", func(t *testing.T) { + RunWithNewEnvironment(t, + chain, func( + ctx fvm.Context, + vm fvm.VM, + snapshot snapshot.SnapshotTree, + testContract *TestContract, + testAccount *EOATestAccount, + ) { + sc := systemcontracts.SystemContractsForChain(chain.ChainID()) + + data := testContract.MakeCallData(t, "store", big.NewInt(42)) + + // The dry-run tx uses nonce 0 (doesn't consume it). + dryTx := gethTypes.NewTransaction( + testAccount.Nonce(), + testContract.DeployedAt.ToCommon(), + big.NewInt(0), + uint64(50_000), + big.NewInt(0), + data, + ) + dryTxBytes, err := dryTx.MarshalBinary() + require.NoError(t, err) + + // The real tx is signed and uses the same nonce. + realTxBytes := testAccount.PrepareSignAndEncodeTx(t, + testContract.DeployedAt.ToCommon(), + data, + big.NewInt(0), + uint64(50_000), + big.NewInt(0), + ) + + code := []byte(fmt.Sprintf(` + import EVM from %s + + transaction(dryTx: [UInt8], realTx: [UInt8], coinbaseBytes: [UInt8; 20]) { + prepare(account: &Account) { + let from = EVM.EVMAddress(bytes: coinbaseBytes) + let coinbase = EVM.EVMAddress(bytes: coinbaseBytes) + + let dryResult = EVM.dryRun(tx: dryTx, from: from) + assert(dryResult.status == EVM.Status.successful, message: "dry run failed") + + let runResult = EVM.run(tx: realTx, coinbase: coinbase) + assert(runResult.status == EVM.Status.successful, message: "run after dry run failed") + } + } + `, sc.EVMContract.Address.HexWithPrefix())) + + dryTxArg := cadence.NewArray( + unittest.BytesToCdcUInt8(dryTxBytes), + ).WithType(stdlib.EVMTransactionBytesCadenceType) + + realTxArg := cadence.NewArray( + unittest.BytesToCdcUInt8(realTxBytes), + ).WithType(stdlib.EVMTransactionBytesCadenceType) + + coinbase := cadence.NewArray( + unittest.BytesToCdcUInt8(testAccount.Address().Bytes()), + ).WithType(stdlib.EVMAddressBytesCadenceType) + + txBody, err := flow.NewTransactionBodyBuilder(). + SetScript(code). + SetPayer(sc.FlowServiceAccount.Address). + AddAuthorizer(sc.FlowServiceAccount.Address). + AddArgument(json.MustEncode(dryTxArg)). + AddArgument(json.MustEncode(realTxArg)). + AddArgument(json.MustEncode(coinbase)). + Build() + require.NoError(t, err) + + tx := fvm.Transaction(txBody, 0) + _, output, err := vm.Run(ctx, tx, snapshot) + require.NoError(t, err) + require.NoError(t, output.Err) + }, + ) + }) } func TestDryCall(t *testing.T) { From 3896575c078bd3b26b31894e5093db956d0e141a Mon Sep 17 00:00:00 2001 From: Janez Podhostnik Date: Mon, 13 Apr 2026 14:51:04 +0200 Subject: [PATCH 05/10] reduce block store interface size --- fvm/environment/evm_block_store.go | 35 ----------- fvm/environment/facade_env.go | 43 +++----------- fvm/environment/mock/environment.go | 77 ------------------------- fvm/environment/mock/evm_block_store.go | 77 ------------------------- fvm/evm/backends/wrappedEnv.go | 11 ---- fvm/evm/testutils/backend.go | 24 -------- 6 files changed, 7 insertions(+), 260 deletions(-) diff --git a/fvm/environment/evm_block_store.go b/fvm/environment/evm_block_store.go index d6b010e6580..4b8d61641b7 100644 --- a/fvm/environment/evm_block_store.go +++ b/fvm/environment/evm_block_store.go @@ -25,16 +25,8 @@ type EVMBlockStore interface { // storage. Persistence happens at transaction end via FlushBlockProposal. StageBlockProposal(*types.BlockProposal) - // FlushBlockProposal writes the cached block proposal to storage. Called by the - // fvm.Environment at the end of each Cadence transaction. - FlushBlockProposal() error - // CommitBlockProposal commits the block proposal and update the chain of blocks CommitBlockProposal(*types.BlockProposal) error - - // Reset discards any staged but unflushed block proposal. Called by the - // fvm.Environment when a transaction fails. - ResetBlockProposal() } const ( @@ -259,30 +251,3 @@ func (bs *BlockStore) FlushBlockProposal() error { return nil } -type NoEVMBlockStore struct{} - -var _ EVMBlockStore = &NoEVMBlockStore{} - -func (bs NoEVMBlockStore) BlockProposal() (*types.BlockProposal, error) { - return nil, nil -} - -func (bs NoEVMBlockStore) CommitBlockProposal(bp *types.BlockProposal) error { - return nil -} - -func (bs NoEVMBlockStore) LatestBlock() (*types.Block, error) { - return nil, nil -} - -func (bs NoEVMBlockStore) BlockHash(height uint64) (gethCommon.Hash, error) { - return gethCommon.Hash{}, nil -} - -func (bs NoEVMBlockStore) ResetBlockProposal() {} - -func (bs NoEVMBlockStore) StageBlockProposal(bp *types.BlockProposal) {} - -func (bs NoEVMBlockStore) FlushBlockProposal() error { - return nil -} diff --git a/fvm/environment/facade_env.go b/fvm/environment/facade_env.go index c5bbd4d606b..1169f6d4d75 100644 --- a/fvm/environment/facade_env.go +++ b/fvm/environment/facade_env.go @@ -3,14 +3,13 @@ package environment import ( "context" - gocommon "github.com/ethereum/go-ethereum/common" "github.com/onflow/cadence/ast" "github.com/onflow/cadence/common" "github.com/onflow/cadence/interpreter" "github.com/onflow/cadence/sema" "github.com/onflow/flow-go/fvm/evm" - "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/fvm/storage" "github.com/onflow/flow-go/fvm/storage/snapshot" "github.com/onflow/flow-go/fvm/storage/state" @@ -54,7 +53,7 @@ type facadeEnvironment struct { *ContractReader ContractUpdater *Programs - EVMBlockStore EVMBlockStore + *BlockStore accounts Accounts txnState storage.TransactionPreparer @@ -151,7 +150,7 @@ func newFacadeEnvironment( common.Address(sc.Crypto.Address), ), ContractUpdater: NoContractUpdater{}, - EVMBlockStore: NoEVMBlockStore{}, + Programs: NewPrograms( tracer, meter, @@ -205,7 +204,7 @@ func NewScriptEnv( params.ScriptInfoParams.ID[:], ) env.addParseRestrictedChecks() - env.EVMBlockStore = NewBlockStore( + env.BlockStore = NewBlockStore( params.Chain.ChainID(), env.ValueStore, env.BlockInfo, @@ -282,7 +281,7 @@ func NewTransactionEnvironment( params.TransactionInfoParams.RandomSourceHistoryCallAllowed, ) - env.EVMBlockStore = NewBlockStore( + env.BlockStore = NewBlockStore( params.Chain.ChainID(), env.ValueStore, env.BlockInfo, @@ -355,7 +354,7 @@ func (env *facadeEnvironment) FlushPendingUpdates() ( if err != nil { return ContractUpdates{}, err } - err = env.EVMBlockStore.FlushBlockProposal() + err = env.BlockStore.FlushBlockProposal() if err != nil { return ContractUpdates{}, err } @@ -366,35 +365,7 @@ func (env *facadeEnvironment) Reset() { env.ContractUpdater.Reset() env.EventEmitter.Reset() env.Programs.Reset() - env.EVMBlockStore.ResetBlockProposal() -} - -func (env *facadeEnvironment) BlockHash(height uint64) (gocommon.Hash, error) { - return env.EVMBlockStore.BlockHash(height) -} - -func (env *facadeEnvironment) BlockProposal() (*types.BlockProposal, error) { - return env.EVMBlockStore.BlockProposal() -} - -func (env *facadeEnvironment) StageBlockProposal(bp *types.BlockProposal) { - env.EVMBlockStore.StageBlockProposal(bp) -} - -func (env *facadeEnvironment) CommitBlockProposal(bp *types.BlockProposal) error { - return env.EVMBlockStore.CommitBlockProposal(bp) -} - -func (env *facadeEnvironment) FlushBlockProposal() error { - return env.EVMBlockStore.FlushBlockProposal() -} - -func (env *facadeEnvironment) LatestBlock() (*types.Block, error) { - return env.EVMBlockStore.LatestBlock() -} - -func (env *facadeEnvironment) ResetBlockProposal() { - env.EVMBlockStore.ResetBlockProposal() + env.BlockStore.ResetBlockProposal() } // Miscellaneous Cadence runtime.Interface API diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index f6e463fcdeb..54c9e3aa103 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -1504,50 +1504,6 @@ func (_c *Environment_Events_Call) RunAndReturn(run func() flow.EventsList) *Env return _c } -// FlushBlockProposal provides a mock function for the type Environment -func (_mock *Environment) FlushBlockProposal() error { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for FlushBlockProposal") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func() error); ok { - r0 = returnFunc() - } else { - r0 = ret.Error(0) - } - return r0 -} - -// Environment_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' -type Environment_FlushBlockProposal_Call struct { - *mock.Call -} - -// FlushBlockProposal is a helper method to define mock.On call -func (_e *Environment_Expecter) FlushBlockProposal() *Environment_FlushBlockProposal_Call { - return &Environment_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} -} - -func (_c *Environment_FlushBlockProposal_Call) Run(run func()) *Environment_FlushBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Environment_FlushBlockProposal_Call) Return(err error) *Environment_FlushBlockProposal_Call { - _c.Call.Return(err) - return _c -} - -func (_c *Environment_FlushBlockProposal_Call) RunAndReturn(run func() error) *Environment_FlushBlockProposal_Call { - _c.Call.Return(run) - return _c -} - // FlushPendingUpdates provides a mock function for the type Environment func (_mock *Environment) FlushPendingUpdates() (environment.ContractUpdates, error) { ret := _mock.Called() @@ -3688,39 +3644,6 @@ func (_c *Environment_Reset_Call) RunAndReturn(run func()) *Environment_Reset_Ca return _c } -// ResetBlockProposal provides a mock function for the type Environment -func (_mock *Environment) ResetBlockProposal() { - _mock.Called() - return -} - -// Environment_ResetBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBlockProposal' -type Environment_ResetBlockProposal_Call struct { - *mock.Call -} - -// ResetBlockProposal is a helper method to define mock.On call -func (_e *Environment_Expecter) ResetBlockProposal() *Environment_ResetBlockProposal_Call { - return &Environment_ResetBlockProposal_Call{Call: _e.mock.On("ResetBlockProposal")} -} - -func (_c *Environment_ResetBlockProposal_Call) Run(run func()) *Environment_ResetBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Environment_ResetBlockProposal_Call) Return() *Environment_ResetBlockProposal_Call { - _c.Call.Return() - return _c -} - -func (_c *Environment_ResetBlockProposal_Call) RunAndReturn(run func()) *Environment_ResetBlockProposal_Call { - _c.Run(run) - return _c -} - // ResolveLocation provides a mock function for the type Environment func (_mock *Environment) ResolveLocation(identifiers []runtime.Identifier, location runtime.Location) ([]runtime.ResolvedLocation, error) { ret := _mock.Called(identifiers, location) diff --git a/fvm/environment/mock/evm_block_store.go b/fvm/environment/mock/evm_block_store.go index 7ebe23991d0..0c59382f5ea 100644 --- a/fvm/environment/mock/evm_block_store.go +++ b/fvm/environment/mock/evm_block_store.go @@ -205,50 +205,6 @@ func (_c *EVMBlockStore_CommitBlockProposal_Call) RunAndReturn(run func(blockPro return _c } -// FlushBlockProposal provides a mock function for the type EVMBlockStore -func (_mock *EVMBlockStore) FlushBlockProposal() error { - ret := _mock.Called() - - if len(ret) == 0 { - panic("no return value specified for FlushBlockProposal") - } - - var r0 error - if returnFunc, ok := ret.Get(0).(func() error); ok { - r0 = returnFunc() - } else { - r0 = ret.Error(0) - } - return r0 -} - -// EVMBlockStore_FlushBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'FlushBlockProposal' -type EVMBlockStore_FlushBlockProposal_Call struct { - *mock.Call -} - -// FlushBlockProposal is a helper method to define mock.On call -func (_e *EVMBlockStore_Expecter) FlushBlockProposal() *EVMBlockStore_FlushBlockProposal_Call { - return &EVMBlockStore_FlushBlockProposal_Call{Call: _e.mock.On("FlushBlockProposal")} -} - -func (_c *EVMBlockStore_FlushBlockProposal_Call) Run(run func()) *EVMBlockStore_FlushBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *EVMBlockStore_FlushBlockProposal_Call) Return(err error) *EVMBlockStore_FlushBlockProposal_Call { - _c.Call.Return(err) - return _c -} - -func (_c *EVMBlockStore_FlushBlockProposal_Call) RunAndReturn(run func() error) *EVMBlockStore_FlushBlockProposal_Call { - _c.Call.Return(run) - return _c -} - // LatestBlock provides a mock function for the type EVMBlockStore func (_mock *EVMBlockStore) LatestBlock() (*types.Block, error) { ret := _mock.Called() @@ -304,39 +260,6 @@ func (_c *EVMBlockStore_LatestBlock_Call) RunAndReturn(run func() (*types.Block, return _c } -// ResetBlockProposal provides a mock function for the type EVMBlockStore -func (_mock *EVMBlockStore) ResetBlockProposal() { - _mock.Called() - return -} - -// EVMBlockStore_ResetBlockProposal_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ResetBlockProposal' -type EVMBlockStore_ResetBlockProposal_Call struct { - *mock.Call -} - -// ResetBlockProposal is a helper method to define mock.On call -func (_e *EVMBlockStore_Expecter) ResetBlockProposal() *EVMBlockStore_ResetBlockProposal_Call { - return &EVMBlockStore_ResetBlockProposal_Call{Call: _e.mock.On("ResetBlockProposal")} -} - -func (_c *EVMBlockStore_ResetBlockProposal_Call) Run(run func()) *EVMBlockStore_ResetBlockProposal_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *EVMBlockStore_ResetBlockProposal_Call) Return() *EVMBlockStore_ResetBlockProposal_Call { - _c.Call.Return() - return _c -} - -func (_c *EVMBlockStore_ResetBlockProposal_Call) RunAndReturn(run func()) *EVMBlockStore_ResetBlockProposal_Call { - _c.Run(run) - return _c -} - // StageBlockProposal provides a mock function for the type EVMBlockStore func (_mock *EVMBlockStore) StageBlockProposal(blockProposal *types.BlockProposal) { _mock.Called(blockProposal) diff --git a/fvm/evm/backends/wrappedEnv.go b/fvm/evm/backends/wrappedEnv.go index 809db1414db..ff4a491a931 100644 --- a/fvm/evm/backends/wrappedEnv.go +++ b/fvm/evm/backends/wrappedEnv.go @@ -255,14 +255,3 @@ func (we *WrappedEnvironment) LatestBlock() (*types.Block, error) { func (we *WrappedEnvironment) StageBlockProposal(bp *types.BlockProposal) { we.env.StageBlockProposal(bp) } - -// FlushBlockProposal implements [Backend]. -func (we *WrappedEnvironment) FlushBlockProposal() error { - err := we.env.FlushBlockProposal() - return handleEnvironmentError(err) -} - -// ResetBlockProposal implements [Backend]. -func (we *WrappedEnvironment) ResetBlockProposal() { - we.env.ResetBlockProposal() -} diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 52ea7055370..428c74504e2 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -255,15 +255,9 @@ func getSimpleBlockStore(chain flow.ChainID, vs *TestValueStore, bi *TestBlockIn StageBlockProposalFunc: func(_bp *types.BlockProposal) { bs.StageBlockProposal(_bp) }, - FlushBlockProposalFunc: func() error { - return bs.FlushBlockProposal() - }, CommitBlockProposalFunc: func(_bp *types.BlockProposal) error { return bs.CommitBlockProposal(_bp) }, - ResetBlockProposalFunc: func() { - bs.ResetBlockProposal() - }, } } @@ -745,8 +739,6 @@ type TestBlockStore struct { CommitBlockProposalFunc func(*types.BlockProposal) error LatestBlockFunc func() (*types.Block, error) StageBlockProposalFunc func(*types.BlockProposal) - FlushBlockProposalFunc func() error - ResetBlockProposalFunc func() } var _ environment.EVMBlockStore = &TestBlockStore{} @@ -790,19 +782,3 @@ func (tb *TestBlockStore) StageBlockProposal(bp *types.BlockProposal) { } stageBlockProposalFunc(bp) } - -func (tb *TestBlockStore) FlushBlockProposal() error { - flushBlockProposalFunc := tb.FlushBlockProposalFunc - if flushBlockProposalFunc == nil { - panic("FlushBlockProposalFunc method is not set") - } - return flushBlockProposalFunc() -} - -func (tb *TestBlockStore) ResetBlockProposal() { - resetFunc := tb.ResetBlockProposalFunc - if resetFunc == nil { - panic("ResetBlockProposalFunc method is not set") - } - resetFunc() -} From a2e6bc9cced0aeefe2829bd7568ff2995148cff6 Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Mon, 20 Apr 2026 12:01:48 -0700 Subject: [PATCH 06/10] add evm block store test case --- fvm/environment/evm_block_store_test.go | 239 ++++++++++++++++++++++++ 1 file changed, 239 insertions(+) diff --git a/fvm/environment/evm_block_store_test.go b/fvm/environment/evm_block_store_test.go index afeeb1210f4..413e9862558 100644 --- a/fvm/environment/evm_block_store_test.go +++ b/fvm/environment/evm_block_store_test.go @@ -13,6 +13,245 @@ import ( "github.com/onflow/flow-go/model/flow" ) +// TestBlockStoreLifecycle tests the block store lifecycle across two Flow blocks, +// following the execution flow documented in BlockStore comments. +// +// This test verifies: +// - Each Cadence tx creates a new BlockStore instance with empty cache +// - Cache hits/misses work correctly within a Cadence tx +// - FlushBlockProposal persists the proposal for the next Cadence tx +// - Reset discards changes on failed Cadence tx +// - CommitBlockProposal finalizes the block and clears LatestBlockProposal +// - Lazy construction of new proposal in the next Flow block +func TestBlockStoreLifecycle(t *testing.T) { + chainID := flow.Testnet + testutils.RunWithTestBackend(t, chainID, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { + + // Gas constants for each EVM tx - using distinct digit positions for easy verification + // Each tx uses a different decimal place, so accumulated values are easy to read + const ( + evmTxAGas = uint64(1) // ones place + evmTxBGas = uint64(20) // tens place + evmTxCGas = uint64(300) // hundreds place + evmTxDGas = uint64(4000) // thousands place + evmTxEGas = uint64(50000) // ten-thousands place + evmTxFGas = uint64(600000) // hundred-thousands place + ) + + // TotalSupply changes for each EVM tx - also using distinct digit positions + // TotalSupply represents native token deposited in EVM (can increase via deposits) + var ( + evmTxASupply = big.NewInt(100) // ones place (x100) + evmTxBSupply = big.NewInt(2000) // tens place (x100) + evmTxESupply = big.NewInt(5000000) // ten-thousands place (x100) + evmTxFSupply = big.NewInt(60000000) // hundred-thousands place (x100) + ) + + // ============================================ + // Flow Block K + // ============================================ + + // --- Cadence tx 1 (succeed) --- + // Each Cadence tx creates a new BlockStore instance + bs1 := environment.NewBlockStore(chainID, backend, backend, backend, root) + + // EVM Tx A: BlockProposal() - cache miss, loads from storage + // Since this is the first ever call, LatestBlockProposal is empty, + // so it reads LatestBlock (genesis) to construct proposal + bpA, err := bs1.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(1), bpA.Height) + expectedParentHash, err := types.GenesisBlock(chainID).Hash() + require.NoError(t, err) + require.Equal(t, expectedParentHash, bpA.ParentBlockHash) + require.Equal(t, uint64(0), bpA.TotalGasUsed) // starts at 0 + + // Verify TotalSupply is inherited from genesis block (0) + require.Equal(t, big.NewInt(0), bpA.TotalSupply) + + // Verify PrevRandao is set (non-zero) - read from RandomGenerator during construction + require.NotEqual(t, gethCommon.Hash{}, bpA.PrevRandao) + prevRandaoBlockK := bpA.PrevRandao // save for later comparison + + // EVM Tx A: StageBlockProposal() - update cache (accumulate gas and supply) + bpA.TotalGasUsed += evmTxAGas + bpA.TotalSupply = new(big.Int).Add(bpA.TotalSupply, evmTxASupply) + bs1.StageBlockProposal(bpA) + + // EVM Tx B: BlockProposal() - cache hit (same BlockStore instance) + bpB, err := bs1.BlockProposal() + require.NoError(t, err) + require.Equal(t, bpA, bpB) // same pointer, cache hit + require.Equal(t, uint64(1), bpB.TotalGasUsed) // sees Tx A's gas + require.Equal(t, big.NewInt(100), bpB.TotalSupply) // sees Tx A's supply + require.Equal(t, prevRandaoBlockK, bpB.PrevRandao) // PrevRandao unchanged within block + + // EVM Tx B: StageBlockProposal() - update cache (accumulate gas and supply) + bpB.TotalGasUsed += evmTxBGas + bpB.TotalSupply = new(big.Int).Add(bpB.TotalSupply, evmTxBSupply) + bs1.StageBlockProposal(bpB) + + // [tx end]: FlushBlockProposal() - write LatestBlockProposal to storage + // At this point, TotalGasUsed = 1 + 20 = 21, TotalSupply = 100 + 2000 = 2100 + require.Equal(t, uint64(21), bpB.TotalGasUsed) + require.Equal(t, big.NewInt(2100), bpB.TotalSupply) + err = bs1.FlushBlockProposal() + require.NoError(t, err) + + // --- Cadence tx 2 (failed) --- + // New BlockStore instance (simulating new Cadence tx) + bs2 := environment.NewBlockStore(chainID, backend, backend, backend, root) + + // EVM Tx C: BlockProposal() - cache miss, loads from storage + // Should load the flushed proposal from tx 1 with accumulated gas = 21, supply = 2100 + bpC, err := bs2.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(21), bpC.TotalGasUsed) // persisted from tx 1 + require.Equal(t, big.NewInt(2100), bpC.TotalSupply) // persisted from tx 1 + require.Equal(t, prevRandaoBlockK, bpC.PrevRandao) // PrevRandao unchanged within block + + // EVM Tx C: StageBlockProposal() - update cache (accumulate gas) + bpC.TotalGasUsed += evmTxCGas + bs2.StageBlockProposal(bpC) + + // EVM Tx D: BlockProposal() - cache hit + bpD, err := bs2.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(321), bpD.TotalGasUsed) // sees A+B+C = 1+20+300 + + // EVM Tx D: StageBlockProposal() - update cache (accumulate gas) + bpD.TotalGasUsed += evmTxDGas + bs2.StageBlockProposal(bpD) + + // Verify all 4 txs' gas is accumulated before revert = 1+20+300+4000 = 4321 + require.Equal(t, uint64(4321), bpD.TotalGasUsed) + + // [tx fail/revert]: Reset() - cache = nil, storage unchanged + bs2.ResetBlockProposal() + + // Verify storage is unchanged by creating a new BlockStore and reading + // Should only see gas from Cadence tx 1 (A+B = 21), not from failed tx 2 (C+D) + bs2Check := environment.NewBlockStore(chainID, backend, backend, backend, root) + bpCheck, err := bs2Check.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(21), bpCheck.TotalGasUsed) // unchanged from tx 1 + require.Equal(t, big.NewInt(2100), bpCheck.TotalSupply) // unchanged from tx 1 + + // --- System chunk tx (last) --- + // New BlockStore instance for system tx + bsSystem := environment.NewBlockStore(chainID, backend, backend, backend, root) + + // heartbeat() -> CommitBlockProposal() + bpCommit, err := bsSystem.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(21), bpCommit.TotalGasUsed) // A+B = 21 + require.Equal(t, big.NewInt(2100), bpCommit.TotalSupply) // A+B supply = 2100 + + // CommitBlockProposal: write LatestBlock, remove LatestBlockProposal + err = bsSystem.CommitBlockProposal(bpCommit) + require.NoError(t, err) + + // Verify LatestBlock is now the committed block with accumulated values + latestBlock, err := bsSystem.LatestBlock() + require.NoError(t, err) + require.Equal(t, uint64(1), latestBlock.Height) + require.Equal(t, uint64(21), latestBlock.TotalGasUsed) + require.Equal(t, big.NewInt(2100), latestBlock.TotalSupply) + require.Equal(t, prevRandaoBlockK, latestBlock.PrevRandao) // PrevRandao preserved in block + + // ============================================ + // Flow Block K+1 + // ============================================ + + // --- Cadence tx 1 --- + // New BlockStore instance (new Flow block, new Cadence tx) + bsK1 := environment.NewBlockStore(chainID, backend, backend, backend, root) + + // EVM Tx E: BlockProposal() - cache miss + // After CommitBlockProposal, LatestBlockProposal was cleared (or new one written) + // This tests lazy construction: reads LatestBlock to construct new proposal + bpE, err := bsK1.BlockProposal() + require.NoError(t, err) + + // Verify the new proposal is a child of the committed block + require.Equal(t, uint64(2), bpE.Height) // height incremented + committedBlockHash, err := latestBlock.Hash() + require.NoError(t, err) + require.Equal(t, committedBlockHash, bpE.ParentBlockHash) // parent is committed block + + // Verify the proposal starts fresh (no accumulated gas from previous block) + require.Equal(t, uint64(0), bpE.TotalGasUsed) + + // Verify TotalSupply is inherited from the committed block (2100) + require.Equal(t, big.NewInt(2100), bpE.TotalSupply) + + // Verify PrevRandao is set and DIFFERENT from block K (new random value for new block) + require.NotEqual(t, gethCommon.Hash{}, bpE.PrevRandao) + require.NotEqual(t, prevRandaoBlockK, bpE.PrevRandao) // different random for new block + prevRandaoBlockK1 := bpE.PrevRandao + + // EVM Tx E: StageBlockProposal() - update cache (accumulate gas and supply) + bpE.TotalGasUsed += evmTxEGas + bpE.TotalSupply = new(big.Int).Add(bpE.TotalSupply, evmTxESupply) + bsK1.StageBlockProposal(bpE) + + // EVM Tx F: BlockProposal() - cache hit + bpF, err := bsK1.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(50000), bpF.TotalGasUsed) // sees Tx E's gas + require.Equal(t, big.NewInt(5002100), bpF.TotalSupply) // 2100 + 5000000 + require.Equal(t, prevRandaoBlockK1, bpF.PrevRandao) // PrevRandao unchanged within block + + // EVM Tx F: StageBlockProposal() - update cache (accumulate gas and supply) + bpF.TotalGasUsed += evmTxFGas + bpF.TotalSupply = new(big.Int).Add(bpF.TotalSupply, evmTxFSupply) + bsK1.StageBlockProposal(bpF) + + // [tx end]: FlushBlockProposal() + // Gas: E+F = 50000+600000 = 650000 + // Supply: 2100 + 5000000 + 60000000 = 65002100 + require.Equal(t, uint64(650000), bpF.TotalGasUsed) + require.Equal(t, big.NewInt(65002100), bpF.TotalSupply) + err = bsK1.FlushBlockProposal() + require.NoError(t, err) + + // --- System chunk tx for block K+1 --- + bsSystemK1 := environment.NewBlockStore(chainID, backend, backend, backend, root) + bpCommitK1, err := bsSystemK1.BlockProposal() + require.NoError(t, err) + require.Equal(t, uint64(650000), bpCommitK1.TotalGasUsed) // E+F = 650000 + require.Equal(t, big.NewInt(65002100), bpCommitK1.TotalSupply) // accumulated supply + + err = bsSystemK1.CommitBlockProposal(bpCommitK1) + require.NoError(t, err) + + // Verify LatestBlock is now block 2 with accumulated values + latestBlockK1, err := bsSystemK1.LatestBlock() + require.NoError(t, err) + require.Equal(t, uint64(2), latestBlockK1.Height) + require.Equal(t, uint64(650000), latestBlockK1.TotalGasUsed) + require.Equal(t, big.NewInt(65002100), latestBlockK1.TotalSupply) + require.Equal(t, prevRandaoBlockK1, latestBlockK1.PrevRandao) // PrevRandao preserved + + // Verify block hashes are correct + hash0, err := bsSystemK1.BlockHash(0) + require.NoError(t, err) + require.Equal(t, types.GenesisBlockHash(chainID), hash0) + + hash1, err := bsSystemK1.BlockHash(1) + require.NoError(t, err) + require.Equal(t, committedBlockHash, hash1) + + hash2, err := bsSystemK1.BlockHash(2) + require.NoError(t, err) + expectedHash2, err := latestBlockK1.Hash() + require.NoError(t, err) + require.Equal(t, expectedHash2, hash2) + }) + }) +} + func TestBlockStore(t *testing.T) { var chainID = flow.Testnet From 9dff1a32dcda431b051cbf50050694f5b43fdfb9 Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Mon, 20 Apr 2026 12:19:52 -0700 Subject: [PATCH 07/10] remove latest block proposal key on commit block proposal --- fvm/environment/evm_block_store.go | 59 ++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 7 deletions(-) diff --git a/fvm/environment/evm_block_store.go b/fvm/environment/evm_block_store.go index 4b8d61641b7..0d3d850ec40 100644 --- a/fvm/environment/evm_block_store.go +++ b/fvm/environment/evm_block_store.go @@ -35,6 +35,50 @@ const ( BlockStoreLatestBlockProposalKey = "LatestBlockProposal" ) +// BlockStore manages EVM block storage and block proposal lifecycle during Flow block execution. +// +// Storage Keys: +// - LatestBlock: The last finalized EVM block. Updated only at CommitBlockProposal(). +// - LatestBlockProposal: The in-progress EVM block accumulating transactions. +// Its parent hash must equal hash(LatestBlock) and height must equal LatestBlock.Height + 1. +// +// Each Cadence transaction creates a new BlockStore instance with an empty cache. +// The cache avoids repeated storage reads within a single Cadence transaction. +// +// Flow Block K Execution: +// +// ├── Cadence tx 1 (succeed) +// │ ├── EVM Tx A +// │ │ ├── BlockProposal() +// │ │ │ ├── cache miss +// │ │ │ ├── read LatestBlockProposal from storage +// │ │ │ │ └── (if empty) read LatestBlock from storage (for parent hash, height) +// │ │ │ └── cache it +// │ │ └── StageBlockProposal() → update cache +// │ ├── EVM Tx B +// │ │ ├── BlockProposal() → cache hit +// │ │ └── StageBlockProposal() → update cache +// │ └── [tx end] +// │ └── FlushBlockProposal() → write LatestBlockProposal, cache = nil +// │ +// ├── Cadence tx 2 (failed) +// │ ├── EVM Tx C +// │ │ ├── BlockProposal() +// │ │ │ ├── cache miss +// │ │ │ └── read LatestBlockProposal from storage → cache it +// │ │ └── StageBlockProposal() → update cache +// │ ├── EVM Tx D +// │ │ ├── BlockProposal() → cache hit +// │ │ └── StageBlockProposal() → update cache +// │ └── [tx fail/revert] +// │ └── Reset() → cache = nil, storage unchanged +// │ +// └── System chunk tx (last) +// └── heartbeat() +// └── CommitBlockProposal() +// ├── write LatestBlock +// ├── remove LatestBlockProposal (new proposal constructed lazily in next flow block) +// └── cache = nil type BlockStore struct { chainID flow.ChainID storage ValueStore @@ -178,16 +222,17 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { return err } - // construct a new block proposal and store - newBP, err := bs.constructBlockProposal() - if err != nil { - return err - } - err = bs.updateBlockProposal(newBP) + // Remove LatestBlockProposal key - the new proposal will be constructed lazily + // on the next BlockProposal() call by reading LatestBlock for parent hash and height. + err = bs.storage.SetValue( + bs.rootAddress[:], + []byte(BlockStoreLatestBlockProposalKey), + nil, // setting to nil removes the key + ) if err != nil { return err } - bs.cached = newBP + bs.cached = nil return nil } From 9d951743e170313cd46bbccabc8b831f7492fd9f Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Tue, 21 Apr 2026 08:46:40 -0700 Subject: [PATCH 08/10] fix lint --- fvm/environment/evm_block_store.go | 1 - 1 file changed, 1 deletion(-) diff --git a/fvm/environment/evm_block_store.go b/fvm/environment/evm_block_store.go index 0d3d850ec40..656e935f124 100644 --- a/fvm/environment/evm_block_store.go +++ b/fvm/environment/evm_block_store.go @@ -295,4 +295,3 @@ func (bs *BlockStore) FlushBlockProposal() error { } return nil } - From d92c0882f9f2fd46c6ed6e0a386ee9b98be0fb15 Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Tue, 21 Apr 2026 09:13:41 -0700 Subject: [PATCH 09/10] fix lint --- fvm/environment/evm_block_store_test.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/fvm/environment/evm_block_store_test.go b/fvm/environment/evm_block_store_test.go index 413e9862558..b7a962db065 100644 --- a/fvm/environment/evm_block_store_test.go +++ b/fvm/environment/evm_block_store_test.go @@ -42,9 +42,9 @@ func TestBlockStoreLifecycle(t *testing.T) { // TotalSupply changes for each EVM tx - also using distinct digit positions // TotalSupply represents native token deposited in EVM (can increase via deposits) var ( - evmTxASupply = big.NewInt(100) // ones place (x100) - evmTxBSupply = big.NewInt(2000) // tens place (x100) - evmTxESupply = big.NewInt(5000000) // ten-thousands place (x100) + evmTxASupply = big.NewInt(100) // ones place (x100) + evmTxBSupply = big.NewInt(2000) // tens place (x100) + evmTxESupply = big.NewInt(5000000) // ten-thousands place (x100) evmTxFSupply = big.NewInt(60000000) // hundred-thousands place (x100) ) @@ -82,10 +82,10 @@ func TestBlockStoreLifecycle(t *testing.T) { // EVM Tx B: BlockProposal() - cache hit (same BlockStore instance) bpB, err := bs1.BlockProposal() require.NoError(t, err) - require.Equal(t, bpA, bpB) // same pointer, cache hit - require.Equal(t, uint64(1), bpB.TotalGasUsed) // sees Tx A's gas - require.Equal(t, big.NewInt(100), bpB.TotalSupply) // sees Tx A's supply - require.Equal(t, prevRandaoBlockK, bpB.PrevRandao) // PrevRandao unchanged within block + require.Equal(t, bpA, bpB) // same pointer, cache hit + require.Equal(t, uint64(1), bpB.TotalGasUsed) // sees Tx A's gas + require.Equal(t, big.NewInt(100), bpB.TotalSupply) // sees Tx A's supply + require.Equal(t, prevRandaoBlockK, bpB.PrevRandao) // PrevRandao unchanged within block // EVM Tx B: StageBlockProposal() - update cache (accumulate gas and supply) bpB.TotalGasUsed += evmTxBGas @@ -199,9 +199,9 @@ func TestBlockStoreLifecycle(t *testing.T) { // EVM Tx F: BlockProposal() - cache hit bpF, err := bsK1.BlockProposal() require.NoError(t, err) - require.Equal(t, uint64(50000), bpF.TotalGasUsed) // sees Tx E's gas - require.Equal(t, big.NewInt(5002100), bpF.TotalSupply) // 2100 + 5000000 - require.Equal(t, prevRandaoBlockK1, bpF.PrevRandao) // PrevRandao unchanged within block + require.Equal(t, uint64(50000), bpF.TotalGasUsed) // sees Tx E's gas + require.Equal(t, big.NewInt(5002100), bpF.TotalSupply) // 2100 + 5000000 + require.Equal(t, prevRandaoBlockK1, bpF.PrevRandao) // PrevRandao unchanged within block // EVM Tx F: StageBlockProposal() - update cache (accumulate gas and supply) bpF.TotalGasUsed += evmTxFGas @@ -220,7 +220,7 @@ func TestBlockStoreLifecycle(t *testing.T) { bsSystemK1 := environment.NewBlockStore(chainID, backend, backend, backend, root) bpCommitK1, err := bsSystemK1.BlockProposal() require.NoError(t, err) - require.Equal(t, uint64(650000), bpCommitK1.TotalGasUsed) // E+F = 650000 + require.Equal(t, uint64(650000), bpCommitK1.TotalGasUsed) // E+F = 650000 require.Equal(t, big.NewInt(65002100), bpCommitK1.TotalSupply) // accumulated supply err = bsSystemK1.CommitBlockProposal(bpCommitK1) From 98cdea499865b8aebb33187a3c4ae4af7dfd2c24 Mon Sep 17 00:00:00 2001 From: "Leo Zhang (zhangchiqing)" Date: Tue, 21 Apr 2026 14:14:17 -0700 Subject: [PATCH 10/10] write new LatestBlockProposal in ComimtBlockProposal --- fvm/environment/evm_block_store.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/fvm/environment/evm_block_store.go b/fvm/environment/evm_block_store.go index 656e935f124..1aab448a129 100644 --- a/fvm/environment/evm_block_store.go +++ b/fvm/environment/evm_block_store.go @@ -77,7 +77,7 @@ const ( // └── heartbeat() // └── CommitBlockProposal() // ├── write LatestBlock -// ├── remove LatestBlockProposal (new proposal constructed lazily in next flow block) +// ├── write new LatestBlockProposal (for next flow block) // └── cache = nil type BlockStore struct { chainID flow.ChainID @@ -222,13 +222,13 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { return err } - // Remove LatestBlockProposal key - the new proposal will be constructed lazily - // on the next BlockProposal() call by reading LatestBlock for parent hash and height. - err = bs.storage.SetValue( - bs.rootAddress[:], - []byte(BlockStoreLatestBlockProposalKey), - nil, // setting to nil removes the key - ) + // Construct and store the new block proposal eagerly to maintain + // state compatibility with the previous implementation. + newBP, err := bs.constructBlockProposal() + if err != nil { + return err + } + err = bs.updateBlockProposal(newBP) if err != nil { return err }