diff --git a/fvm/environment/env.go b/fvm/environment/env.go index b276e3645b4..33d7068e4e2 100644 --- a/fvm/environment/env.go +++ b/fvm/environment/env.go @@ -18,6 +18,8 @@ type Environment interface { Meter + MetricsReporter + // Runtime BorrowCadenceRuntime() *reusableRuntime.ReusableCadenceRuntime ReturnCadenceRuntime(*reusableRuntime.ReusableCadenceRuntime) diff --git a/fvm/environment/mock/environment.go b/fvm/environment/mock/environment.go index aa1b76fd679..82dc5c2bec2 100644 --- a/fvm/environment/mock/environment.go +++ b/fvm/environment/mock/environment.go @@ -476,6 +476,16 @@ func (_m *Environment) DeductTransactionFees(payer flow.Address, inclusionEffort return r0, r1 } +// EVMBlockExecuted provides a mock function with given fields: txCount, totalGasUsed, totalSupplyInFlow +func (_m *Environment) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + _m.Called(txCount, totalGasUsed, totalSupplyInFlow) +} + +// EVMTransactionExecuted provides a mock function with given fields: gasUsed, isDirectCall, failed +func (_m *Environment) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + _m.Called(gasUsed, isDirectCall, failed) +} + // EmitEvent provides a mock function with given fields: _a0 func (_m *Environment) EmitEvent(_a0 cadence.Event) error { ret := _m.Called(_a0) @@ -1489,6 +1499,36 @@ func (_m *Environment) RevokeAccountKey(address common.Address, index uint32) (* return r0, r1 } +// RuntimeSetNumberOfAccounts provides a mock function with given fields: count +func (_m *Environment) RuntimeSetNumberOfAccounts(count uint64) { + _m.Called(count) +} + +// RuntimeTransactionChecked provides a mock function with given fields: _a0 +func (_m *Environment) RuntimeTransactionChecked(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionInterpreted provides a mock function with given fields: _a0 +func (_m *Environment) RuntimeTransactionInterpreted(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionParsed provides a mock function with given fields: _a0 +func (_m *Environment) RuntimeTransactionParsed(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionProgramsCacheHit provides a mock function with given fields: +func (_m *Environment) RuntimeTransactionProgramsCacheHit() { + _m.Called() +} + +// RuntimeTransactionProgramsCacheMiss provides a mock function with given fields: +func (_m *Environment) RuntimeTransactionProgramsCacheMiss() { + _m.Called() +} + // ServiceEvents provides a mock function with given fields: func (_m *Environment) ServiceEvents() flow.EventsList { ret := _m.Called() @@ -1514,6 +1554,11 @@ func (_m *Environment) SetInterpreterSharedState(state *interpreter.SharedState) _m.Called(state) } +// SetNumberOfDeployedCOAs provides a mock function with given fields: count +func (_m *Environment) SetNumberOfDeployedCOAs(count uint64) { + _m.Called(count) +} + // SetValue provides a mock function with given fields: owner, key, value func (_m *Environment) SetValue(owner []byte, key []byte, value []byte) error { ret := _m.Called(owner, key, value) diff --git a/fvm/environment/mock/evm_metrics_reporter.go b/fvm/environment/mock/evm_metrics_reporter.go new file mode 100644 index 00000000000..6790f1796b9 --- /dev/null +++ b/fvm/environment/mock/evm_metrics_reporter.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// EVMMetricsReporter is an autogenerated mock type for the EVMMetricsReporter type +type EVMMetricsReporter struct { + mock.Mock +} + +// EVMBlockExecuted provides a mock function with given fields: txCount, totalGasUsed, totalSupplyInFlow +func (_m *EVMMetricsReporter) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + _m.Called(txCount, totalGasUsed, totalSupplyInFlow) +} + +// EVMTransactionExecuted provides a mock function with given fields: gasUsed, isDirectCall, failed +func (_m *EVMMetricsReporter) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + _m.Called(gasUsed, isDirectCall, failed) +} + +// SetNumberOfDeployedCOAs provides a mock function with given fields: count +func (_m *EVMMetricsReporter) SetNumberOfDeployedCOAs(count uint64) { + _m.Called(count) +} + +// NewEVMMetricsReporter creates a new instance of EVMMetricsReporter. 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 NewEVMMetricsReporter(t interface { + mock.TestingT + Cleanup(func()) +}) *EVMMetricsReporter { + mock := &EVMMetricsReporter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/fvm/environment/mock/metrics_reporter.go b/fvm/environment/mock/metrics_reporter.go index dbc2b014974..1b5e02ce902 100644 --- a/fvm/environment/mock/metrics_reporter.go +++ b/fvm/environment/mock/metrics_reporter.go @@ -13,6 +13,16 @@ type MetricsReporter struct { mock.Mock } +// EVMBlockExecuted provides a mock function with given fields: txCount, totalGasUsed, totalSupplyInFlow +func (_m *MetricsReporter) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + _m.Called(txCount, totalGasUsed, totalSupplyInFlow) +} + +// EVMTransactionExecuted provides a mock function with given fields: gasUsed, isDirectCall, failed +func (_m *MetricsReporter) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + _m.Called(gasUsed, isDirectCall, failed) +} + // RuntimeSetNumberOfAccounts provides a mock function with given fields: count func (_m *MetricsReporter) RuntimeSetNumberOfAccounts(count uint64) { _m.Called(count) @@ -43,6 +53,11 @@ func (_m *MetricsReporter) RuntimeTransactionProgramsCacheMiss() { _m.Called() } +// SetNumberOfDeployedCOAs provides a mock function with given fields: count +func (_m *MetricsReporter) SetNumberOfDeployedCOAs(count uint64) { + _m.Called(count) +} + // NewMetricsReporter creates a new instance of MetricsReporter. 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 NewMetricsReporter(t interface { diff --git a/fvm/environment/mock/runtime_metrics_reporter.go b/fvm/environment/mock/runtime_metrics_reporter.go new file mode 100644 index 00000000000..bd04a4294e7 --- /dev/null +++ b/fvm/environment/mock/runtime_metrics_reporter.go @@ -0,0 +1,58 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// RuntimeMetricsReporter is an autogenerated mock type for the RuntimeMetricsReporter type +type RuntimeMetricsReporter struct { + mock.Mock +} + +// RuntimeSetNumberOfAccounts provides a mock function with given fields: count +func (_m *RuntimeMetricsReporter) RuntimeSetNumberOfAccounts(count uint64) { + _m.Called(count) +} + +// RuntimeTransactionChecked provides a mock function with given fields: _a0 +func (_m *RuntimeMetricsReporter) RuntimeTransactionChecked(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionInterpreted provides a mock function with given fields: _a0 +func (_m *RuntimeMetricsReporter) RuntimeTransactionInterpreted(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionParsed provides a mock function with given fields: _a0 +func (_m *RuntimeMetricsReporter) RuntimeTransactionParsed(_a0 time.Duration) { + _m.Called(_a0) +} + +// RuntimeTransactionProgramsCacheHit provides a mock function with given fields: +func (_m *RuntimeMetricsReporter) RuntimeTransactionProgramsCacheHit() { + _m.Called() +} + +// RuntimeTransactionProgramsCacheMiss provides a mock function with given fields: +func (_m *RuntimeMetricsReporter) RuntimeTransactionProgramsCacheMiss() { + _m.Called() +} + +// NewRuntimeMetricsReporter creates a new instance of RuntimeMetricsReporter. 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 NewRuntimeMetricsReporter(t interface { + mock.TestingT + Cleanup(func()) +}) *RuntimeMetricsReporter { + mock := &RuntimeMetricsReporter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/fvm/environment/program_logger.go b/fvm/environment/program_logger.go index 639e2dde9ac..b5308ca2abd 100644 --- a/fvm/environment/program_logger.go +++ b/fvm/environment/program_logger.go @@ -12,9 +12,16 @@ import ( "github.com/onflow/flow-go/module/trace" ) -// MetricsReporter captures and reports metrics to back to the execution +// MetricsReporter captures and reports EVM metrics to back to the execution +type EVMMetricsReporter interface { + SetNumberOfDeployedCOAs(count uint64) + EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) + EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) +} + +// RuntimeMetricsReporter captures and reports runtime metrics to back to the execution // environment it is a setup passed to the context. -type MetricsReporter interface { +type RuntimeMetricsReporter interface { RuntimeTransactionParsed(time.Duration) RuntimeTransactionChecked(time.Duration) RuntimeTransactionInterpreted(time.Duration) @@ -23,9 +30,18 @@ type MetricsReporter interface { RuntimeTransactionProgramsCacheHit() } +// MetricsReporter captures and reports metrics to back to the execution +// environment it is a setup passed to the context. +type MetricsReporter interface { + EVMMetricsReporter + RuntimeMetricsReporter +} + // NoopMetricsReporter is a MetricReporter that does nothing. type NoopMetricsReporter struct{} +var _ MetricsReporter = &NoopMetricsReporter{} + // RuntimeTransactionParsed is a noop func (NoopMetricsReporter) RuntimeTransactionParsed(time.Duration) {} @@ -44,6 +60,15 @@ func (NoopMetricsReporter) RuntimeTransactionProgramsCacheMiss() {} // RuntimeTransactionProgramsCacheHit is a noop func (NoopMetricsReporter) RuntimeTransactionProgramsCacheHit() {} +// SetNumberOfDeployedCOAs is a noop +func (NoopMetricsReporter) SetNumberOfDeployedCOAs(_ uint64) {} + +// EVMTransactionExecuted is a noop +func (NoopMetricsReporter) EVMTransactionExecuted(_ uint64, _ bool, _ bool) {} + +// EVMBlockExecuted is a noop +func (NoopMetricsReporter) EVMBlockExecuted(_ int, _ uint64, _ float64) {} + type ProgramLoggerParams struct { zerolog.Logger diff --git a/fvm/environment/programs_test.go b/fvm/environment/programs_test.go index c1d1d19e3b4..9c8d50402cf 100644 --- a/fvm/environment/programs_test.go +++ b/fvm/environment/programs_test.go @@ -893,6 +893,12 @@ func (m *metricsReporter) RuntimeTransactionInterpreted(duration time.Duration) func (m *metricsReporter) RuntimeSetNumberOfAccounts(count uint64) {} +func (m *metricsReporter) SetNumberOfDeployedCOAs(count uint64) {} + +func (m *metricsReporter) EVMTransactionExecuted(_ uint64, _ bool, _ bool) {} + +func (m *metricsReporter) EVMBlockExecuted(_ int, _ uint64, _ float64) {} + func (m *metricsReporter) RuntimeTransactionProgramsCacheMiss() { m.CacheMisses++ } diff --git a/fvm/evm/backends/wrappedEnv.go b/fvm/evm/backends/wrappedEnv.go index a996dff05da..c366b6ba14a 100644 --- a/fvm/evm/backends/wrappedEnv.go +++ b/fvm/evm/backends/wrappedEnv.go @@ -154,6 +154,26 @@ func (we *WrappedEnvironment) StartChildSpan( return we.env.StartChildSpan(name, options...) } +func (we *WrappedEnvironment) SetNumberOfDeployedCOAs(count uint64) { + we.env.SetNumberOfDeployedCOAs(count) +} + +func (we *WrappedEnvironment) EVMTransactionExecuted( + gasUsed uint64, + isDirectCall bool, + failed bool, +) { + we.env.EVMTransactionExecuted(gasUsed, isDirectCall, failed) +} + +func (we *WrappedEnvironment) EVMBlockExecuted( + txCount int, + totalGasUsed uint64, + totalSupplyInFlow float64, +) { + we.env.EVMBlockExecuted(txCount, totalGasUsed, totalSupplyInFlow) +} + func handleEnvironmentError(err error) error { if err == nil { return nil diff --git a/fvm/evm/handler/blockHashList.go b/fvm/evm/handler/blockHashList.go new file mode 100644 index 00000000000..cd5bda0caa2 --- /dev/null +++ b/fvm/evm/handler/blockHashList.go @@ -0,0 +1,260 @@ +package handler + +import ( + "encoding/binary" + "fmt" + + gethCommon "github.com/onflow/go-ethereum/common" + + "github.com/onflow/flow-go/fvm/evm/types" + "github.com/onflow/flow-go/model/flow" +) + +const ( + blockHashListMetaKey = "BlockHashListMeta" + blockHashListBucketKeyFormat = "BlockHashListBucket%d" + + hashCountPerBucket = 16 + hashEncodingSize = 32 + capacityEncodingSize = 8 + tailEncodingSize = 8 + countEncodingSize = 8 + heightEncodingSize = 8 + metaEncodingSize = capacityEncodingSize + + tailEncodingSize + + countEncodingSize + + heightEncodingSize +) + +// BlockHashList stores the last `capacity` number of block hashes +// +// Under the hood it breaks the list of hashes into +// smaller fixed size buckets to minimize the +// number of bytes read and written during set/get operations. +type BlockHashList struct { + backend types.Backend + rootAddress flow.Address + + // cached meta data + capacity int + tail int // index to write to + count int // number of elements (count <= capacity) + height uint64 // keeps the height of last added block +} + +// NewBlockHashList creates a block hash list +// It tries to load the metadata from the backend +// and if not exist it creates one +func NewBlockHashList( + backend types.Backend, + rootAddress flow.Address, + capacity int, +) (*BlockHashList, error) { + bhl := &BlockHashList{ + backend: backend, + rootAddress: rootAddress, + capacity: capacity, + tail: 0, + count: 0, + height: 0, + } + err := bhl.loadMetaData() + if err != nil { + return nil, err + } + // check the loaded capacity against the one provided + if bhl.capacity != capacity { + return nil, fmt.Errorf( + "capacity doesn't match, expected: %d, got: %d", + bhl.capacity, + capacity, + ) + } + return bhl, nil +} + +// Push pushes a block hash for the next height to the list. +// If the list has reached to the capacity, it overwrites the oldest element. +func (bhl *BlockHashList) Push(height uint64, bh gethCommon.Hash) error { + // handle the very first block + if bhl.IsEmpty() && height != 0 { + return fmt.Errorf("out of the order block hash, expected: 0, got: %d", height) + } + // check the block heights before pushing + if !bhl.IsEmpty() && height != bhl.height+1 { + return fmt.Errorf("out of the order block hash, expected: %d, got: %d", bhl.height+1, height) + } + // updates the block hash stored at index + err := bhl.updateBlockHashAt(bhl.tail, bh) + if err != nil { + return err + } + + // update meta data + bhl.tail = (bhl.tail + 1) % bhl.capacity + bhl.height = height + if bhl.count != bhl.capacity { + bhl.count++ + } + return bhl.storeMetaData() +} + +// IsEmpty returns true if the list is empty +func (bhl *BlockHashList) IsEmpty() bool { + return bhl.count == 0 +} + +// LastAddedBlockHash returns the last block hash added to the list +// for empty list it returns empty hash value +func (bhl *BlockHashList) LastAddedBlockHash() (gethCommon.Hash, error) { + if bhl.IsEmpty() { + // return empty hash + return gethCommon.Hash{}, nil + } + index := (bhl.tail + bhl.capacity - 1) % bhl.capacity + return bhl.getBlockHashAt(index) +} + +// MinAvailableHeight returns the min available height in the list +func (bhl *BlockHashList) MinAvailableHeight() uint64 { + if bhl.IsEmpty() { + return 0 + } + return bhl.height - (uint64(bhl.count) - 1) +} + +// MaxAvailableHeight returns the max available height in the list +func (bhl *BlockHashList) MaxAvailableHeight() uint64 { + return bhl.height +} + +// BlockHashByIndex returns the block hash by block height +func (bhl *BlockHashList) BlockHashByHeight(height uint64) (found bool, bh gethCommon.Hash, err error) { + if bhl.IsEmpty() || + height > bhl.MaxAvailableHeight() || + height < bhl.MinAvailableHeight() { + return false, gethCommon.Hash{}, nil + } + // calculate the index to lookup + diff := bhl.height - height + index := (bhl.tail - int(diff) - 1 + bhl.capacity) % bhl.capacity + bh, err = bhl.getBlockHashAt(index) + return true, bh, err +} + +// updateBlockHashAt updates block hash at index +func (bhl *BlockHashList) updateBlockHashAt(idx int, bh gethCommon.Hash) error { + // fetch the bucket + bucketNumber := idx / hashCountPerBucket + bucket, err := bhl.fetchBucket(bucketNumber) + if err != nil { + return err + } + // update the block hash + start := (idx % hashCountPerBucket) * hashEncodingSize + end := start + hashEncodingSize + copy(bucket[start:end], bh.Bytes()) + + // store bucket + return bhl.backend.SetValue( + bhl.rootAddress[:], + []byte(fmt.Sprintf(blockHashListBucketKeyFormat, bucketNumber)), + bucket, + ) +} + +// fetchBucket fetches the bucket +func (bhl *BlockHashList) fetchBucket(num int) ([]byte, error) { + data, err := bhl.backend.GetValue( + bhl.rootAddress[:], + []byte(fmt.Sprintf(blockHashListBucketKeyFormat, num)), + ) + if err != nil { + return nil, err + } + // if not exist create and return an new empty buffer + if len(data) == 0 { + return make([]byte, hashCountPerBucket*hashEncodingSize), nil + } + return data, err +} + +// returns the block hash at the given index +func (bhl *BlockHashList) getBlockHashAt(idx int) (gethCommon.Hash, error) { + // fetch the bucket first + bucket, err := bhl.fetchBucket(idx / hashCountPerBucket) + if err != nil { + return gethCommon.Hash{}, err + } + // return the hash from the bucket + start := (idx % hashCountPerBucket) * hashEncodingSize + end := start + hashEncodingSize + return gethCommon.BytesToHash(bucket[start:end]), nil +} + +// loadMetaData loads the meta data from the storage +func (bhl *BlockHashList) loadMetaData() error { + data, err := bhl.backend.GetValue( + bhl.rootAddress[:], + []byte(blockHashListMetaKey), + ) + if err != nil { + return err + } + // if data doesn't exist + // return and keep the default values + if len(data) == 0 { + return nil + } + // check the data size + if len(data) < metaEncodingSize { + return fmt.Errorf("encoded input too short: %d < %d", len(data), metaEncodingSize) + } + + pos := 0 + // decode capacity + bhl.capacity = int(binary.BigEndian.Uint64(data[pos:])) + pos += capacityEncodingSize + + // decode tail + bhl.tail = int(binary.BigEndian.Uint64(data[pos:])) + pos += tailEncodingSize + + // decode count + bhl.count = int(binary.BigEndian.Uint64(data[pos:])) + pos += countEncodingSize + + // decode height + bhl.height = binary.BigEndian.Uint64(data[pos:]) + + return nil +} + +// storeMetaData stores the meta data into storage +func (bhl *BlockHashList) storeMetaData() error { + // encode meta data + buffer := make([]byte, metaEncodingSize) + pos := 0 + + // encode capacity + binary.BigEndian.PutUint64(buffer[pos:], uint64(bhl.capacity)) + pos += capacityEncodingSize + + // encode tail + binary.BigEndian.PutUint64(buffer[pos:], uint64(bhl.tail)) + pos += tailEncodingSize + + // encode count + binary.BigEndian.PutUint64(buffer[pos:], uint64(bhl.count)) + pos += countEncodingSize + + // encode height + binary.BigEndian.PutUint64(buffer[pos:], bhl.height) + + // store the encoded data into backend + return bhl.backend.SetValue( + bhl.rootAddress[:], + []byte(blockHashListMetaKey), + buffer, + ) +} diff --git a/fvm/evm/handler/blockHashList_test.go b/fvm/evm/handler/blockHashList_test.go new file mode 100644 index 00000000000..75a7f682f3a --- /dev/null +++ b/fvm/evm/handler/blockHashList_test.go @@ -0,0 +1,98 @@ +package handler_test + +import ( + "testing" + + gethCommon "github.com/onflow/go-ethereum/common" + "github.com/stretchr/testify/require" + + "github.com/onflow/flow-go/fvm/evm/handler" + "github.com/onflow/flow-go/fvm/evm/testutils" + "github.com/onflow/flow-go/model/flow" +) + +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) + require.NoError(t, err) + require.True(t, bhl.IsEmpty()) + + h, err := bhl.LastAddedBlockHash() + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{}, h) + + found, h, err := bhl.BlockHashByHeight(0) + require.False(t, found) + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{}, h) + + // first add blocks for the full range of capacity + for i := 0; i < capacity; i++ { + err := bhl.Push(uint64(i), gethCommon.Hash{byte(i)}) + require.NoError(t, err) + require.Equal(t, uint64(0), bhl.MinAvailableHeight()) + require.Equal(t, uint64(i), bhl.MaxAvailableHeight()) + h, err := bhl.LastAddedBlockHash() + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{byte(i)}, h) + } + + // check the value for all of them + for i := 0; i < capacity; i++ { + found, h, err := bhl.BlockHashByHeight(uint64(i)) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, gethCommon.Hash{byte(i)}, h) + } + h, err = bhl.LastAddedBlockHash() + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{byte(capacity - 1)}, h) + + // over the border additions + for i := capacity; i < capacity+3; i++ { + err := bhl.Push(uint64(i), gethCommon.Hash{byte(i)}) + require.NoError(t, err) + require.Equal(t, uint64(i-capacity+1), bhl.MinAvailableHeight()) + require.Equal(t, uint64(i), bhl.MaxAvailableHeight()) + } + // check that old block has been replaced + for i := 0; i < 3; i++ { + found, _, err := bhl.BlockHashByHeight(uint64(i)) + require.NoError(t, err) + require.False(t, found) + } + // check the rest of blocks + for i := 3; i < capacity+3; i++ { + found, h, err := bhl.BlockHashByHeight(uint64(i)) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, gethCommon.Hash{byte(i)}, h) + } + h, err = bhl.LastAddedBlockHash() + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{byte(capacity + 2)}, h) + + // construct a new one and check + bhl, err = handler.NewBlockHashList(backend, root, capacity) + require.NoError(t, err) + require.False(t, bhl.IsEmpty()) + + h2, err := bhl.LastAddedBlockHash() + require.NoError(t, err) + require.Equal(t, h, h2) + + require.Equal(t, uint64(3), bhl.MinAvailableHeight()) + require.Equal(t, uint64(capacity+2), bhl.MaxAvailableHeight()) + + // check all the stored blocks + for i := 3; i < capacity+3; i++ { + found, h, err := bhl.BlockHashByHeight(uint64(i)) + require.NoError(t, err) + require.True(t, found) + require.Equal(t, gethCommon.Hash{byte(i)}, h) + } + }) + }) +} diff --git a/fvm/evm/handler/blockstore.go b/fvm/evm/handler/blockstore.go index 5a4c8cad303..52e8eb020ac 100644 --- a/fvm/evm/handler/blockstore.go +++ b/fvm/evm/handler/blockstore.go @@ -11,7 +11,7 @@ import ( ) const ( - BlockHashListCapacity = 16 + BlockHashListCapacity = 256 BlockStoreLatestBlockKey = "LatestBlock" BlockStoreLatestBlockProposalKey = "LatestBlockProposal" BlockStoreBlockHashesKey = "LatestBlockHashes" @@ -121,7 +121,11 @@ func (bs *BlockStore) CommitBlockProposal(bp *types.BlockProposal) error { return err } - err = bs.updateBlockHashList(bp.Block.Height, hash) + bhl, err := bs.getBlockHashList() + if err != nil { + return err + } + err = bhl.Push(bp.Block.Height, hash) if err != nil { return err } @@ -152,38 +156,74 @@ func (bs *BlockStore) BlockHash(height uint64) (gethCommon.Hash, error) { if err != nil { return gethCommon.Hash{}, err } - _, hash := bhl.BlockHashByHeight(height) - return hash, nil + _, hash, err := bhl.BlockHashByHeight(height) + return hash, err +} + +func (bs *BlockStore) getBlockHashList() (*BlockHashList, error) { + // check legacy block hash list first + return bs.checkLegacyAndMigrate() + // TODO: when preview net is out, we can remove the call to legacy and uncomment below + // BlockStoreBlockHashesKey constant also be removed + // + // bhl, err := NewBlockHashList(bs.backend, bs.rootAddress, BlockHashListCapacity) + // if err != nil { + // return nil, err + // } + // if bhl.IsEmpty() { + // err = bhl.Push(types.GenesisBlock.Height, types.GenesisBlockHash) + // if err != nil { + // return nil, err + // } + // } + // return bhl, nil } -func (bs *BlockStore) getBlockHashList() (*types.BlockHashList, error) { +func (bs *BlockStore) checkLegacyAndMigrate() (*BlockHashList, error) { data, err := bs.backend.GetValue(bs.rootAddress[:], []byte(BlockStoreBlockHashesKey)) if err != nil { return nil, err } + + // no legacy found if len(data) == 0 { - bhl := types.NewBlockHashList(BlockHashListCapacity) - err = bhl.Push(types.GenesisBlock.Height, types.GenesisBlockHash) - return bhl, err + bhl, err := NewBlockHashList(bs.backend, bs.rootAddress, BlockHashListCapacity) + if err != nil { + return nil, err + } + if bhl.IsEmpty() { + err = bhl.Push(types.GenesisBlock.Height, types.GenesisBlockHash) + if err != nil { + return nil, err + } + } + return bhl, nil } - return types.NewBlockHashListFromEncoded(data) -} -func (bs *BlockStore) updateBlockHashList(height uint64, hash gethCommon.Hash) error { - bhl, err := bs.getBlockHashList() + legacy, err := types.NewBlockHashListFromEncoded(data) if err != nil { - return err + return nil, err } - err = bhl.Push(height, hash) + + // migrate the data + bhl, err := NewBlockHashList(bs.backend, bs.rootAddress, BlockHashListCapacity) if err != nil { - return err + return nil, err } - err = bs.backend.SetValue( - bs.rootAddress[:], - []byte(BlockStoreBlockHashesKey), - bhl.Encode()) + for i := uint64(0); i <= legacy.MaxAvailableHeight(); i++ { + // for the non-existing ones we insert empty hash + _, bh := legacy.BlockHashByHeight(i) + err = bhl.Push(i, bh) + if err != nil { + return nil, err + } + } + + // reset the old key + err = bs.backend.SetValue(bs.rootAddress[:], []byte(BlockStoreBlockHashesKey), nil) if err != nil { - return err + return nil, err } - return nil + + return bhl, nil } diff --git a/fvm/evm/handler/blockstore_test.go b/fvm/evm/handler/blockstore_test.go index 0b322ad8ea6..a5123f5d558 100644 --- a/fvm/evm/handler/blockstore_test.go +++ b/fvm/evm/handler/blockstore_test.go @@ -57,7 +57,7 @@ func TestBlockStore(t *testing.T) { bp.TotalSupply = supply err = bs.UpdateBlockProposal(bp) require.NoError(t, err) - // this should still return the gensis block + // this should still return the genesis block retb, err := bs.LatestBlock() require.NoError(t, err) require.Equal(t, types.GenesisBlock, retb) @@ -97,6 +97,41 @@ func TestBlockStore(t *testing.T) { } +// TODO: we can remove this when the previewnet is out +func TestBlockStoreMigration(t *testing.T) { + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) { + legacyCapacity := 16 + maxHeightAdded := 32 + legacy := types.NewBlockHashList(16) + for i := 0; i <= maxHeightAdded; i++ { + err := legacy.Push(uint64(i), gethCommon.Hash{byte(i)}) + require.NoError(t, err) + } + err := backend.SetValue( + root[:], + []byte(handler.BlockStoreBlockHashesKey), + legacy.Encode(), + ) + require.NoError(t, err) + bs := handler.NewBlockStore(backend, root) + + for i := 0; i <= maxHeightAdded-legacyCapacity; i++ { + h, err := bs.BlockHash(uint64(i)) + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{}, h) + } + + for i := maxHeightAdded - legacyCapacity + 1; i <= maxHeightAdded; i++ { + h, err := bs.BlockHash(uint64(i)) + require.NoError(t, err) + require.Equal(t, gethCommon.Hash{byte(i)}, h) + } + }) + }) + +} + // This test reproduces a state before a breaking change on the Block type, // which added a timestamp and total gas used, // then it adds new blocks and makes sure the retrival diff --git a/fvm/evm/handler/handler.go b/fvm/evm/handler/handler.go index 9bc14a2f2d0..cfbbee525c8 100644 --- a/fvm/evm/handler/handler.go +++ b/fvm/evm/handler/handler.go @@ -85,19 +85,21 @@ func (h *ContractHandler) deployCOA(uuid uint64) (*types.Result, error) { factory := h.addressAllocator.COAFactoryAddress() factoryAccount := h.AccountByAddress(factory, false) + factoryNonce := factoryAccount.Nonce() call := types.NewDeployCallWithTargetAddress( factory, target, coa.ContractBytes, uint64(gaslimit), new(big.Int), - factoryAccount.Nonce(), + factoryNonce, ) ctx, err := h.getBlockContext() if err != nil { return nil, err } + h.backend.SetNumberOfDeployedCOAs(factoryNonce) return h.executeAndHandleCall(ctx, call, nil, false) } @@ -194,7 +196,7 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres return nil, err } - // saftey check for result + // safety check for result if len(res) == 0 { return nil, types.ErrUnexpectedEmptyResult } @@ -232,6 +234,13 @@ func (h *ContractHandler) batchRun(rlpEncodedTxs [][]byte, coinbase types.Addres return nil, err } + // step 4 - update metrics + h.backend.EVMTransactionExecuted( + r.GasConsumed, + false, + r.Failed(), + ) + h.tracer.Collect(r.TxHash) } @@ -268,6 +277,13 @@ func (h *ContractHandler) commitBlockProposal() error { return err } + // report metrics + h.backend.EVMBlockExecuted( + len(bp.TxHashes), + bp.TotalGasUsed, + types.UnsafeCastOfBalanceToFloat64(bp.TotalSupply), + ) + return nil } @@ -335,7 +351,14 @@ func (h *ContractHandler) run( return nil, err } - // step 5 - collect traces + // step 4 - update metrics + h.backend.EVMTransactionExecuted( + res.GasConsumed, + false, + res.Failed(), + ) + + // step 6 - collect traces h.tracer.Collect(res.TxHash) return res, nil } @@ -527,6 +550,13 @@ func (h *ContractHandler) executeAndHandleCall( return nil, err } + // metric + h.backend.EVMTransactionExecuted( + res.GasConsumed, + true, + res.Failed(), + ) + // collect traces h.tracer.Collect(res.TxHash) return res, nil diff --git a/fvm/evm/handler/handler_test.go b/fvm/evm/handler/handler_test.go index 4af2e161fd0..cdb024ce89c 100644 --- a/fvm/evm/handler/handler_test.go +++ b/fvm/evm/handler/handler_test.go @@ -1344,6 +1344,99 @@ func TestHandler_TransactionRun(t *testing.T) { }) } +func TestHandler_Metrics(t *testing.T) { + t.Parallel() + + testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) { + testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) { + testutils.RunWithEOATestAccount(t, backend, rootAddr, func(eoa *testutils.EOATestAccount) { + result := &types.Result{ + GasConsumed: testutils.RandomGas(1000), + DeployedContractAddress: &types.EmptyAddress, + } + em := &testutils.TestEmulator{ + RunTransactionFunc: func(tx *gethTypes.Transaction) (*types.Result, error) { + return result, nil + }, + BatchRunTransactionFunc: func(txs []*gethTypes.Transaction) ([]*types.Result, error) { + return []*types.Result{result, result}, nil + }, + DirectCallFunc: func(call *types.DirectCall) (*types.Result, error) { + return result, nil + }, + NonceOfFunc: func(address types.Address) (uint64, error) { + return 1, nil + }, + } + handler := handler.NewContractHandler( + flow.Emulator, + rootAddr, + flowTokenAddress, + rootAddr, + handler.NewBlockStore(backend, rootAddr), + handler.NewAddressAllocator(), + backend, + em, + debug.NopTracer) + + tx := eoa.PrepareSignAndEncodeTx( + t, + gethCommon.Address{}, + nil, + nil, + 100_000, + big.NewInt(1), + ) + // run tx + called := 0 + backend.EVMTransactionExecutedFunc = func(gas uint64, isDirect, failed bool) { + require.Equal(t, result.GasConsumed, gas) + require.False(t, isDirect) + require.False(t, failed) + called += 1 + } + handler.Run(tx, types.EmptyAddress) + require.Equal(t, 1, called) + + // batch run + backend.EVMTransactionExecutedFunc = func(gas uint64, isDirect, failed bool) { + require.Equal(t, result.GasConsumed, gas) + require.False(t, isDirect) + require.False(t, failed) + called += 1 + } + handler.BatchRun([][]byte{tx, tx}, types.EmptyAddress) + require.Equal(t, 3, called) + + // Direct call + backend.EVMTransactionExecutedFunc = func(gas uint64, isDirect, failed bool) { + require.Equal(t, result.GasConsumed, gas) + require.True(t, isDirect) + require.False(t, failed) + called += 1 + } + coaCounter := 0 + backend.SetNumberOfDeployedCOAsFunc = func(count uint64) { + coaCounter = int(count) + } + handler.DeployCOA(0) + require.Equal(t, 4, called) + require.Equal(t, 1, coaCounter) + + // form block + backend.EVMBlockExecutedFunc = func(txCount int, gasUsed uint64, totalSupply float64) { + require.Equal(t, 4, txCount) + require.Equal(t, result.GasConsumed*4, gasUsed) + require.Equal(t, float64(0), totalSupply) + called += 1 + } + handler.CommitBlockProposal() + require.Equal(t, 5, called) + }) + }) + }) +} + // returns true if error passes the checks type checkError func(error) bool diff --git a/fvm/evm/testutils/backend.go b/fvm/evm/testutils/backend.go index 498fd2948b0..0d377b2637c 100644 --- a/fvm/evm/testutils/backend.go +++ b/fvm/evm/testutils/backend.go @@ -44,6 +44,7 @@ func RunWithTestBackend(t testing.TB, f func(*TestBackend)) { TestRandomGenerator: getSimpleRandomGenerator(), TestContractFunctionInvoker: &TestContractFunctionInvoker{}, TestTracer: &TestTracer{}, + TestMetricsReporter: &TestMetricsReporter{}, } f(tb) } @@ -193,6 +194,7 @@ type TestBackend struct { *TestContractFunctionInvoker *testUUIDGenerator *TestTracer + *TestMetricsReporter } var _ types.Backend = &TestBackend{} @@ -532,3 +534,30 @@ func (tt *TestTracer) ExpectedSpan(t *testing.T, expected trace.SpanName) { return tracing.NewMockTracerSpan() } } + +type TestMetricsReporter struct { + SetNumberOfDeployedCOAsFunc func(uint64) + EVMTransactionExecutedFunc func(uint64, bool, bool) + EVMBlockExecutedFunc func(int, uint64, float64) +} + +var _ environment.EVMMetricsReporter = &TestMetricsReporter{} + +func (tmr *TestMetricsReporter) SetNumberOfDeployedCOAs(count uint64) { + // call the method if available otherwise skip + if tmr.SetNumberOfDeployedCOAsFunc != nil { + tmr.SetNumberOfDeployedCOAsFunc(count) + } +} +func (tmr *TestMetricsReporter) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + // call the method if available otherwise skip + if tmr.EVMTransactionExecutedFunc != nil { + tmr.EVMTransactionExecutedFunc(gasUsed, isDirectCall, failed) + } +} +func (tmr *TestMetricsReporter) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + // call the method if available otherwise skip + if tmr.EVMBlockExecutedFunc != nil { + tmr.EVMBlockExecutedFunc(txCount, totalGasUsed, totalSupplyInFlow) + } +} diff --git a/fvm/evm/types/backend.go b/fvm/evm/types/backend.go index 23fd2c5e377..7f955179ad4 100644 --- a/fvm/evm/types/backend.go +++ b/fvm/evm/types/backend.go @@ -16,4 +16,5 @@ type Backend interface { environment.ContractFunctionInvoker environment.UUIDGenerator environment.Tracer + environment.EVMMetricsReporter } diff --git a/fvm/evm/types/balance.go b/fvm/evm/types/balance.go index b002e309cfd..e31d8966609 100644 --- a/fvm/evm/types/balance.go +++ b/fvm/evm/types/balance.go @@ -58,10 +58,28 @@ func BalanceToBigInt(bal Balance) *big.Int { return (*big.Int)(bal) } +// UnsafeCastOfBalanceToFloat64 tries to cast the balance into a float64, +// +// Warning! this method is only provided for logging and metric reporting +// purposes, using float64 for any actual computation result in non-determinism. +func UnsafeCastOfBalanceToFloat64(bal Balance) float64 { + res, _ := new(big.Float).Quo( + new(big.Float).SetInt(bal), + new(big.Float).SetInt( + new(big.Int).Exp( + big.NewInt(10), + big.NewInt(int64(AttoScale)), + nil, + ), + ), + ).Float64() + return res +} + // ConvertBalanceToUFix64 casts the balance into a UFix64, // // Warning! The smallest unit of Flow token that a FlowVault (Cadence) could store is 1e10^-8, -// so transfering smaller values (or values with smalls fractions) could result in loss in +// so transferring smaller values (or values with smalls fractions) could result in loss in // conversion. The rounded flag should be used to prevent loss of assets. func ConvertBalanceToUFix64(bal Balance) (value cadence.UFix64, roundedOff bool, err error) { converted := new(big.Int).Div(bal, UFixToAttoConversionMultiplier) @@ -70,7 +88,6 @@ func ConvertBalanceToUFix64(bal Balance) (value cadence.UFix64, roundedOff bool, err = fmt.Errorf("balance can't be casted to a uint64") } return cadence.UFix64(converted.Uint64()), BalanceConvertionToUFix64ProneToRoundingError(bal), err - } // BalanceConvertionToUFix64ProneToRoundingError returns true diff --git a/fvm/evm/types/balance_test.go b/fvm/evm/types/balance_test.go index c13d7d0d870..05604a62a3a 100644 --- a/fvm/evm/types/balance_test.go +++ b/fvm/evm/types/balance_test.go @@ -37,4 +37,12 @@ func TestBalance(t *testing.T) { require.NoError(t, err) require.Equal(t, cadence.UFix64(0), bret) require.True(t, roundedOff) + + // test unsafe conversion to float64 + u, err = cadence.NewUFix64("100.0002") + require.NoError(t, err) + bal = types.NewBalanceFromUFix64(u) + require.NoError(t, err) + ret := types.UnsafeCastOfBalanceToFloat64(bal) + require.Equal(t, 100.0002, ret) } diff --git a/fvm/evm/types/blockHashList.go b/fvm/evm/types/blockHashList.go index c5b1f203899..3a1b5756768 100644 --- a/fvm/evm/types/blockHashList.go +++ b/fvm/evm/types/blockHashList.go @@ -20,6 +20,7 @@ const ( ) // BlockHashList holds the last `capacity` number of block hashes in the list +// TODO: Clean up, BlockHashList would become deprecated when previewnet is out. type BlockHashList struct { blocks []gethCommon.Hash capacity int diff --git a/module/metrics.go b/module/metrics.go index fe7e3ee228e..11f182c9837 100644 --- a/module/metrics.go +++ b/module/metrics.go @@ -823,6 +823,15 @@ type RuntimeMetrics interface { RuntimeTransactionProgramsCacheHit() } +type EVMMetrics interface { + // SetNumberOfDeployedCOAs sets the total number of deployed COAs + SetNumberOfDeployedCOAs(count uint64) + // EVMTransactionExecuted reports the gas used when executing an evm transaction + EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) + // EVMBlockExecuted reports the block size, total gas used and total supply when executing an evm block + EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) +} + type ProviderMetrics interface { // ChunkDataPackRequestProcessed is executed every time a chunk data pack request is picked up for processing at execution node. // It increases the request processed counter by one. @@ -947,6 +956,7 @@ func (stats *BlockExecutionResultStats) Add(other CollectionExecutionResultStats type ExecutionMetrics interface { LedgerMetrics RuntimeMetrics + EVMMetrics ProviderMetrics WALMetrics diff --git a/module/metrics/execution.go b/module/metrics/execution.go index 67a5aec84f3..7419ec3014a 100644 --- a/module/metrics/execution.go +++ b/module/metrics/execution.go @@ -87,6 +87,15 @@ type ExecutionCollector struct { maxCollectionHeight prometheus.Gauge computationResultUploadedCount prometheus.Counter computationResultUploadRetriedCount prometheus.Counter + numberOfDeployedCOAs prometheus.Gauge + evmBlockTotalSupply prometheus.Gauge + totalExecutedEVMTransactionsCounter prometheus.Counter + totalFailedEVMTransactionsCounter prometheus.Counter + totalExecutedEVMDirectCallsCounter prometheus.Counter + totalFailedEVMDirectCallsCounter prometheus.Counter + evmTransactionGasUsed prometheus.Histogram + evmBlockTxCount prometheus.Histogram + evmBlockGasUsed prometheus.Histogram } func NewExecutionCollector(tracer module.Tracer) *ExecutionCollector { @@ -695,12 +704,79 @@ func NewExecutionCollector(tracer module.Tracer) *ExecutionCollector { }), maxCollectionHeightData: counters.NewMonotonousCounter(0), + maxCollectionHeight: prometheus.NewGauge(prometheus.GaugeOpts{ Name: "max_collection_height", Namespace: namespaceExecution, Subsystem: subsystemIngestion, Help: "gauge to track the maximum block height of collections received", }), + + numberOfDeployedCOAs: promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "number_of_deployed_coas", + Help: "the number of deployed coas", + }), + + totalExecutedEVMTransactionsCounter: promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "total_executed_evm_transaction_count", + Help: "the total number of executed evm transactions (including direct calls)", + }), + + totalFailedEVMTransactionsCounter: promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "total_failed_evm_transaction_count", + Help: "the total number of executed evm transactions with failed status (including direct calls)", + }), + + totalExecutedEVMDirectCallsCounter: promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "total_executed_evm_direct_call_count", + Help: "the total number of executed evm direct calls", + }), + + totalFailedEVMDirectCallsCounter: promauto.NewCounter(prometheus.CounterOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "total_failed_evm_direct_call_count", + Help: "the total number of executed evm direct calls with failed status.", + }), + + evmTransactionGasUsed: promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "evm_transaction_gas_used", + Help: "the total amount of gas used by a transaction", + Buckets: prometheus.ExponentialBuckets(20_000, 2, 8), + }), + + evmBlockTxCount: promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "evm_block_transaction_counts", + Help: "the total number of transactions per evm block", + Buckets: prometheus.ExponentialBuckets(1, 2, 8), + }), + + evmBlockGasUsed: promauto.NewHistogram(prometheus.HistogramOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "evm_block_gas_used", + Help: "the total amount of gas used by a block", + Buckets: prometheus.ExponentialBuckets(100_000, 2, 8), + }), + + evmBlockTotalSupply: promauto.NewGauge(prometheus.GaugeOpts{ + Namespace: namespaceExecution, + Subsystem: subsystemEVM, + Name: "evm_block_total_supply", + Help: "the total amount of flow deposited to EVM (in FLOW)", + }), } return ec @@ -959,6 +1035,38 @@ func (ec *ExecutionCollector) RuntimeTransactionProgramsCacheHit() { ec.programsCacheHit.Inc() } +func (ec *ExecutionCollector) SetNumberOfDeployedCOAs(count uint64) { + ec.numberOfDeployedCOAs.Set(float64(count)) +} + +func (ec *ExecutionCollector) EVMTransactionExecuted( + gasUsed uint64, + isDirectCall bool, + failed bool, +) { + ec.totalExecutedEVMTransactionsCounter.Inc() + if isDirectCall { + ec.totalExecutedEVMDirectCallsCounter.Inc() + if failed { + ec.totalFailedEVMDirectCallsCounter.Inc() + } + } + if failed { + ec.totalFailedEVMTransactionsCounter.Inc() + } + ec.evmTransactionGasUsed.Observe(float64(gasUsed)) +} + +func (ec *ExecutionCollector) EVMBlockExecuted( + txCount int, + totalGasUsed uint64, + totalSupplyInFlow float64, +) { + ec.evmBlockTxCount.Observe(float64(txCount)) + ec.evmBlockGasUsed.Observe(float64(totalGasUsed)) + ec.evmBlockTotalSupply.Set(totalSupplyInFlow) +} + func (ec *ExecutionCollector) UpdateCollectionMaxHeight(height uint64) { updated := ec.maxCollectionHeightData.Set(height) if updated { diff --git a/module/metrics/namespaces.go b/module/metrics/namespaces.go index 2f2c75d5255..a35885db7dc 100644 --- a/module/metrics/namespaces.go +++ b/module/metrics/namespaces.go @@ -72,6 +72,7 @@ const ( subsystemMTrie = "mtrie" subsystemIngestion = "ingestion" subsystemRuntime = "runtime" + subsystemEVM = "evm" subsystemProvider = "provider" subsystemBlockDataUploader = "block_data_uploader" ) diff --git a/module/metrics/noop.go b/module/metrics/noop.go index 729e1280588..5016876d1f7 100644 --- a/module/metrics/noop.go +++ b/module/metrics/noop.go @@ -199,6 +199,9 @@ func (nc *NoopCollector) RuntimeTransactionInterpreted(dur time.Duration) func (nc *NoopCollector) RuntimeSetNumberOfAccounts(count uint64) {} func (nc *NoopCollector) RuntimeTransactionProgramsCacheMiss() {} func (nc *NoopCollector) RuntimeTransactionProgramsCacheHit() {} +func (nc *NoopCollector) SetNumberOfDeployedCOAs(_ uint64) {} +func (nc *NoopCollector) EVMTransactionExecuted(_ uint64, _ bool, _ bool) {} +func (nc *NoopCollector) EVMBlockExecuted(_ int, _ uint64, _ float64) {} func (nc *NoopCollector) ScriptExecuted(dur time.Duration, size int) {} func (nc *NoopCollector) ScriptExecutionErrorLocal() {} func (nc *NoopCollector) ScriptExecutionErrorOnExecutionNode() {} diff --git a/module/mock/evm_metrics.go b/module/mock/evm_metrics.go new file mode 100644 index 00000000000..105fe622bbb --- /dev/null +++ b/module/mock/evm_metrics.go @@ -0,0 +1,39 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package mock + +import mock "github.com/stretchr/testify/mock" + +// EVMMetrics is an autogenerated mock type for the EVMMetrics type +type EVMMetrics struct { + mock.Mock +} + +// EVMBlockExecuted provides a mock function with given fields: txCount, totalGasUsed, totalSupplyInFlow +func (_m *EVMMetrics) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + _m.Called(txCount, totalGasUsed, totalSupplyInFlow) +} + +// EVMTransactionExecuted provides a mock function with given fields: gasUsed, isDirectCall, failed +func (_m *EVMMetrics) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + _m.Called(gasUsed, isDirectCall, failed) +} + +// SetNumberOfDeployedCOAs provides a mock function with given fields: count +func (_m *EVMMetrics) SetNumberOfDeployedCOAs(count uint64) { + _m.Called(count) +} + +// NewEVMMetrics creates a new instance of EVMMetrics. 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 NewEVMMetrics(t interface { + mock.TestingT + Cleanup(func()) +}) *EVMMetrics { + mock := &EVMMetrics{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/module/mock/execution_metrics.go b/module/mock/execution_metrics.go index 407ee922a9a..6adc14e02a2 100644 --- a/module/mock/execution_metrics.go +++ b/module/mock/execution_metrics.go @@ -21,6 +21,16 @@ func (_m *ExecutionMetrics) ChunkDataPackRequestProcessed() { _m.Called() } +// EVMBlockExecuted provides a mock function with given fields: txCount, totalGasUsed, totalSupplyInFlow +func (_m *ExecutionMetrics) EVMBlockExecuted(txCount int, totalGasUsed uint64, totalSupplyInFlow float64) { + _m.Called(txCount, totalGasUsed, totalSupplyInFlow) +} + +// EVMTransactionExecuted provides a mock function with given fields: gasUsed, isDirectCall, failed +func (_m *ExecutionMetrics) EVMTransactionExecuted(gasUsed uint64, isDirectCall bool, failed bool) { + _m.Called(gasUsed, isDirectCall, failed) +} + // ExecutionBlockCachedPrograms provides a mock function with given fields: programs func (_m *ExecutionMetrics) ExecutionBlockCachedPrograms(programs int) { _m.Called(programs) @@ -206,6 +216,11 @@ func (_m *ExecutionMetrics) RuntimeTransactionProgramsCacheMiss() { _m.Called() } +// SetNumberOfDeployedCOAs provides a mock function with given fields: count +func (_m *ExecutionMetrics) SetNumberOfDeployedCOAs(count uint64) { + _m.Called(count) +} + // StartBlockReceivedToExecuted provides a mock function with given fields: blockID func (_m *ExecutionMetrics) StartBlockReceivedToExecuted(blockID flow.Identifier) { _m.Called(blockID)