From 3cd5faa5cb3e52b5d95a899d9df1eac786d67df9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Fri, 12 Jul 2024 15:02:53 -0700 Subject: [PATCH 1/3] add meter method which returns remaining computation for given computation kind --- fvm/environment/meter.go | 14 +++++++------- fvm/meter/computation_meter.go | 14 ++++++++++++++ fvm/meter/meter_test.go | 21 +++++++++++++++++++++ fvm/storage/state/execution_state.go | 15 +++++++++++++++ fvm/storage/state/transaction_state.go | 5 +++++ 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/fvm/environment/meter.go b/fvm/environment/meter.go index ad7c28c7eb3..2cfa958da57 100644 --- a/fvm/environment/meter.go +++ b/fvm/environment/meter.go @@ -3,6 +3,7 @@ package environment import ( "context" + "github.com/onflow/cadence/runtime" "github.com/onflow/cadence/runtime/common" "github.com/onflow/flow-go/fvm/errors" @@ -70,18 +71,13 @@ var MainnetExecutionEffortWeights = meter.ExecutionEffortWeights{ } type Meter interface { - MeterComputation(common.ComputationKind, uint) error - ComputationUsed() (uint64, error) + runtime.MeterInterface + ComputationIntensities() meter.MeteredComputationIntensities ComputationAvailable(common.ComputationKind, uint) bool - MeterMemory(usage common.MemoryUsage) error - MemoryUsed() (uint64, error) - MeterEmittedEvent(byteSize uint64) error TotalEmittedEventBytes() uint64 - - InteractionUsed() (uint64, error) } type meterImpl struct { @@ -112,6 +108,10 @@ func (meter *meterImpl) ComputationAvailable( return meter.txnState.ComputationAvailable(kind, intensity) } +func (meter *meterImpl) ComputationRemaining(kind common.ComputationKind) uint { + return meter.txnState.ComputationRemaining(kind) +} + func (meter *meterImpl) ComputationUsed() (uint64, error) { return meter.txnState.TotalComputationUsed(), nil } diff --git a/fvm/meter/computation_meter.go b/fvm/meter/computation_meter.go index 4966650d748..103d1492b75 100644 --- a/fvm/meter/computation_meter.go +++ b/fvm/meter/computation_meter.go @@ -116,10 +116,24 @@ func (m *ComputationMeter) ComputationAvailable( if !ok { return true } + potentialComputationUsage := m.computationUsed + w*uint64(intensity) return potentialComputationUsage <= m.params.computationLimit } +// ComputationRemaining returns the remaining computation left in the transaction for the given type +func (m *ComputationMeter) ComputationRemaining(kind common.ComputationKind) uint { + w, ok := m.params.computationWeights[kind] + // if not found return has capacity + // given the behaviour of MeterComputation is ignoring intensities without a set weight + if !ok { + return math.MaxUint + } + + remainingComputationUsage := m.params.computationLimit - m.computationUsed + return uint(remainingComputationUsage / w) +} + // ComputationIntensities returns all the measured computational intensities func (m *ComputationMeter) ComputationIntensities() MeteredComputationIntensities { return m.computationIntensities diff --git a/fvm/meter/meter_test.go b/fvm/meter/meter_test.go index 4234966c260..82812c3dc1b 100644 --- a/fvm/meter/meter_test.go +++ b/fvm/meter/meter_test.go @@ -139,6 +139,27 @@ func TestWeightedComputationMetering(t *testing.T) { require.True(t, m.ComputationAvailable(1, 10)) }) + t.Run("check computation remaining", func(t *testing.T) { + m := meter.NewMeter( + meter.DefaultParameters(). + WithComputationLimit(10). + WithComputationWeights( + map[common.ComputationKind]uint64{0: 1 << meter.MeterExecutionInternalPrecisionBytes}), + ) + + remaining := m.ComputationRemaining(0) + require.Equal(t, uint(10), remaining) + + err := m.MeterComputation(0, 1) + require.NoError(t, err) + require.Equal(t, uint64(1), m.TotalComputationUsed()) + + require.Equal(t, uint(9), m.ComputationRemaining(0)) + + // test a type without a weight (default zero) + require.Equal(t, uint(math.MaxUint), m.ComputationRemaining(1)) + }) + t.Run("merge meters", func(t *testing.T) { compKind := common.ComputationKind(0) m := meter.NewMeter( diff --git a/fvm/storage/state/execution_state.go b/fvm/storage/state/execution_state.go index 909b91b374f..e9b1d2221ef 100644 --- a/fvm/storage/state/execution_state.go +++ b/fvm/storage/state/execution_state.go @@ -2,6 +2,7 @@ package state import ( "fmt" + "math" "github.com/onflow/cadence/runtime/common" "github.com/onflow/crypto/hash" @@ -235,6 +236,20 @@ func (state *ExecutionState) ComputationAvailable(kind common.ComputationKind, i return true } +// ComputationRemaining returns the available computation capacity without metering +func (state *ExecutionState) ComputationRemaining(kind common.ComputationKind) uint { + if state.finalized { + // if state is finalized return 0 + return 0 + } + + if state.enforceLimits { + return state.meter.ComputationRemaining(kind) + } + + return math.MaxUint +} + // TotalComputationUsed returns total computation used func (state *ExecutionState) TotalComputationUsed() uint64 { return state.meter.TotalComputationUsed() diff --git a/fvm/storage/state/transaction_state.go b/fvm/storage/state/transaction_state.go index 2a75c632bbc..8670a3d326e 100644 --- a/fvm/storage/state/transaction_state.go +++ b/fvm/storage/state/transaction_state.go @@ -22,6 +22,7 @@ func (id NestedTransactionId) StateForTestingOnly() *ExecutionState { type Meter interface { MeterComputation(kind common.ComputationKind, intensity uint) error ComputationAvailable(kind common.ComputationKind, intensity uint) bool + ComputationRemaining(kind common.ComputationKind) uint ComputationIntensities() meter.MeteredComputationIntensities TotalComputationLimit() uint TotalComputationUsed() uint64 @@ -451,6 +452,10 @@ func (txnState *transactionState) ComputationAvailable( return txnState.current().ComputationAvailable(kind, intensity) } +func (txnState *transactionState) ComputationRemaining(kind common.ComputationKind) uint { + return txnState.current().ComputationRemaining(kind) +} + func (txnState *transactionState) MeterMemory( kind common.MemoryKind, intensity uint, From f075976943eb85ee0b111e4c89285bb45367af99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 16 Oct 2024 15:37:19 -0700 Subject: [PATCH 2/3] handle usage > limit --- fvm/meter/computation_meter.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/fvm/meter/computation_meter.go b/fvm/meter/computation_meter.go index 103d1492b75..b859ad38ec5 100644 --- a/fvm/meter/computation_meter.go +++ b/fvm/meter/computation_meter.go @@ -121,7 +121,7 @@ func (m *ComputationMeter) ComputationAvailable( return potentialComputationUsage <= m.params.computationLimit } -// ComputationRemaining returns the remaining computation left in the transaction for the given type +// ComputationRemaining returns the remaining computation (intensity) left in the transaction for the given type func (m *ComputationMeter) ComputationRemaining(kind common.ComputationKind) uint { w, ok := m.params.computationWeights[kind] // if not found return has capacity @@ -131,6 +131,10 @@ func (m *ComputationMeter) ComputationRemaining(kind common.ComputationKind) uin } remainingComputationUsage := m.params.computationLimit - m.computationUsed + if remainingComputationUsage <= 0 { + return 0 + } + return uint(remainingComputationUsage / w) } From e8d75b6e1b07ac6c578be746e3c73190b7a6d924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20M=C3=BCller?= Date: Wed, 16 Oct 2024 15:46:58 -0700 Subject: [PATCH 3/3] update mocks --- fvm/environment/mock/meter.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/fvm/environment/mock/meter.go b/fvm/environment/mock/meter.go index a156b02f0ef..ef5498a8d08 100644 --- a/fvm/environment/mock/meter.go +++ b/fvm/environment/mock/meter.go @@ -137,9 +137,9 @@ func (_m *Meter) MemoryUsed() (uint64, error) { return r0, r1 } -// MeterComputation provides a mock function with given fields: _a0, _a1 -func (_m *Meter) MeterComputation(_a0 common.ComputationKind, _a1 uint) error { - ret := _m.Called(_a0, _a1) +// MeterComputation provides a mock function with given fields: operationType, intensity +func (_m *Meter) MeterComputation(operationType common.ComputationKind, intensity uint) error { + ret := _m.Called(operationType, intensity) if len(ret) == 0 { panic("no return value specified for MeterComputation") @@ -147,7 +147,7 @@ func (_m *Meter) MeterComputation(_a0 common.ComputationKind, _a1 uint) error { var r0 error if rf, ok := ret.Get(0).(func(common.ComputationKind, uint) error); ok { - r0 = rf(_a0, _a1) + r0 = rf(operationType, intensity) } else { r0 = ret.Error(0) }