From 5ee9f3388e988a949424128dce0b6bb622086dfc Mon Sep 17 00:00:00 2001 From: amirylm Date: Wed, 24 Jan 2024 19:01:16 +0200 Subject: [PATCH] performables limit: use a heuristic to calculate number of perfomables - doing encoding once upon init - includes a large buffer as we use max values for all fields --- pkg/v3/observation.go | 7 +- pkg/v3/observation_test.go | 69 +++-------- pkg/v3/outcome.go | 5 +- pkg/v3/outcome_test.go | 63 ++++++----- pkg/v3/plugin/hooks/add_from_staging.go | 23 +++- pkg/v3/plugin/hooks/add_from_staging_test.go | 63 ++++++----- pkg/v3/plugin/ocr3.go | 10 +- pkg/v3/plugin/ocr3_test.go | 14 ++- pkg/v3/plugin/performable.go | 13 ++- pkg/v3/plugin/performable_test.go | 113 ++++++++++--------- pkg/v3/types/len.go | 82 ++++++++++++++ 11 files changed, 274 insertions(+), 188 deletions(-) create mode 100644 pkg/v3/types/len.go diff --git a/pkg/v3/observation.go b/pkg/v3/observation.go index 8a56697a..aacf5423 100644 --- a/pkg/v3/observation.go +++ b/pkg/v3/observation.go @@ -14,7 +14,6 @@ import ( // as different nodes would upgrade at different times and would need to // adhere to each others' limits const ( - ObservationPerformablesLimit = 50 ObservationLogRecoveryProposalsLimit = 5 ObservationConditionalsProposalsLimit = 5 ObservationBlockHistoryLimit = 256 @@ -48,6 +47,9 @@ func (observation AutomationObservation) Encode() ([]byte, error) { } func DecodeAutomationObservation(data []byte, utg types.UpkeepTypeGetter, wg types.WorkIDGenerator) (AutomationObservation, error) { + if len(data) > MaxObservationLength { + return AutomationObservation{}, fmt.Errorf("observation length cannot be greater than %d", MaxObservationLength) + } ao := AutomationObservation{} err := json.Unmarshal(data, &ao) if err != nil { @@ -75,9 +77,6 @@ func validateAutomationObservation(o AutomationObservation, utg types.UpkeepType } // Validate Performables - if (len(o.Performable)) > ObservationPerformablesLimit { - return fmt.Errorf("performable length cannot be greater than %d", ObservationPerformablesLimit) - } seenPerformables := make(map[string]bool) for _, res := range o.Performable { if err := validateCheckResult(res, utg, wg); err != nil { diff --git a/pkg/v3/observation_test.go b/pkg/v3/observation_test.go index 3665efd7..2651d693 100644 --- a/pkg/v3/observation_test.go +++ b/pkg/v3/observation_test.go @@ -152,26 +152,33 @@ func TestDuplicateBlockHistory(t *testing.T) { assert.ErrorContains(t, err, "block history cannot have duplicate block numbers") } -func TestLargePerformable(t *testing.T) { +func TestLargeObservation(t *testing.T) { ao := AutomationObservation{ Performable: []commontypes.CheckResult{}, UpkeepProposals: []commontypes.CoordinatedBlockProposal{validConditionalProposal, validLogProposal}, BlockHistory: validBlockHistory, } - for i := 0; i < ObservationPerformablesLimit+1; i++ { - newConditionalResult := validConditionalResult + size := types.BlockHistorySize(ao.BlockHistory) + size += types.CoordinatedBlockProposalSize(len(ao.UpkeepProposals)) + + for i := 0; size < MaxObservationLength*2; i++ { uid := commontypes.UpkeepIdentifier{} uid.FromBigInt(big.NewInt(int64(i + 1))) - newConditionalResult.UpkeepID = uid - newConditionalResult.WorkID = mockWorkIDGenerator(newConditionalResult.UpkeepID, newConditionalResult.Trigger) - ao.Performable = append(ao.Performable, newConditionalResult) + newResult := validLogResult + if mockUpkeepTypeGetter(uid) == types.ConditionTrigger { + newResult = validConditionalResult + } + newResult.UpkeepID = uid + newResult.WorkID = mockWorkIDGenerator(newResult.UpkeepID, newResult.Trigger) + ao.Performable = append(ao.Performable, newResult) + size += types.CheckResultSize(newResult) } encoded, err := ao.Encode() assert.NoError(t, err, "no error in encoding valid automation observation") _, err = DecodeAutomationObservation(encoded, mockUpkeepTypeGetter, mockWorkIDGenerator) assert.Error(t, err) - assert.ErrorContains(t, err, "performable length cannot be greater than") + assert.ErrorContains(t, err, "observation length cannot be greater than") } func TestDuplicatePerformable(t *testing.T) { @@ -564,54 +571,6 @@ func TestInvalidLogProposal(t *testing.T) { assert.ErrorContains(t, err, "log trigger extension cannot be empty for log upkeep") } -func TestLargeObservationSize(t *testing.T) { - ao := AutomationObservation{ - Performable: []commontypes.CheckResult{}, - UpkeepProposals: []commontypes.CoordinatedBlockProposal{}, - BlockHistory: commontypes.BlockHistory{}, - } - for i := 0; i < ObservationBlockHistoryLimit; i++ { - ao.BlockHistory = append(ao.BlockHistory, commontypes.BlockKey{ - Number: commontypes.BlockNumber(i + 1), - Hash: [32]byte{1}, - }) - } - largePerformData := [10001]byte{} - for i := 0; i < ObservationPerformablesLimit; i++ { - newResult := validLogResult - uid := commontypes.UpkeepIdentifier{} - uid.FromBigInt(big.NewInt(int64(i + 10001))) - newResult.UpkeepID = uid - newResult.WorkID = mockWorkIDGenerator(newResult.UpkeepID, newResult.Trigger) - newResult.PerformData = largePerformData[:] - ao.Performable = append(ao.Performable, newResult) - } - for i := 0; i < ObservationConditionalsProposalsLimit; i++ { - newProposal := validConditionalProposal - uid := commontypes.UpkeepIdentifier{} - uid.FromBigInt(big.NewInt(int64(i + 1))) - newProposal.UpkeepID = uid - newProposal.WorkID = mockWorkIDGenerator(newProposal.UpkeepID, newProposal.Trigger) - ao.UpkeepProposals = append(ao.UpkeepProposals, newProposal) - } - for i := 0; i < ObservationLogRecoveryProposalsLimit; i++ { - newProposal := validLogProposal - uid := commontypes.UpkeepIdentifier{} - uid.FromBigInt(big.NewInt(int64(i + 1001))) - newProposal.UpkeepID = uid - newProposal.WorkID = mockWorkIDGenerator(newProposal.UpkeepID, newProposal.Trigger) - ao.UpkeepProposals = append(ao.UpkeepProposals, newProposal) - } - encoded, err := ao.Encode() - assert.NoError(t, err, "no error in encoding valid automation observation") - - decoded, err := DecodeAutomationObservation(encoded, mockUpkeepTypeGetter, mockWorkIDGenerator) - assert.NoError(t, err, "no error in decoding valid automation observation") - - assert.Equal(t, ao, decoded, "final result from encoding and decoding should match") - assert.Less(t, len(encoded), MaxObservationLength, "encoded observation should be less than maxObservationSize") -} - func mockUpkeepTypeGetter(id commontypes.UpkeepIdentifier) types.UpkeepType { if id == conditionalUpkeepID { return types.ConditionTrigger diff --git a/pkg/v3/outcome.go b/pkg/v3/outcome.go index 05963a58..42f67888 100644 --- a/pkg/v3/outcome.go +++ b/pkg/v3/outcome.go @@ -55,7 +55,7 @@ type AutomationOutcome struct { func validateAutomationOutcome(o AutomationOutcome, utg types.UpkeepTypeGetter, wg types.WorkIDGenerator) error { // Validate AgreedPerformables if (len(o.AgreedPerformables)) > OutcomeAgreedPerformablesLimit { - return fmt.Errorf("outcome performable length cannot be greater than %d", OutcomeAgreedPerformablesLimit) + return fmt.Errorf("outcome length cannot be greater than %d", OutcomeAgreedPerformablesLimit) } seenPerformables := make(map[string]bool) for _, res := range o.AgreedPerformables { @@ -100,6 +100,9 @@ func (outcome AutomationOutcome) Encode() ([]byte, error) { // DecodeAutomationOutcome decodes an AutomationOutcome from an encoded array // of bytes. Possible errors come from the encoding/json package func DecodeAutomationOutcome(data []byte, utg types.UpkeepTypeGetter, wg types.WorkIDGenerator) (AutomationOutcome, error) { + if len(data) > MaxOutcomeLength { + return AutomationOutcome{}, fmt.Errorf("outcome length cannot be greater than %d", MaxOutcomeLength) + } ao := AutomationOutcome{} err := json.Unmarshal(data, &ao) if err != nil { diff --git a/pkg/v3/outcome_test.go b/pkg/v3/outcome_test.go index d93ae36a..6c4ceaca 100644 --- a/pkg/v3/outcome_test.go +++ b/pkg/v3/outcome_test.go @@ -9,12 +9,13 @@ import ( "github.com/stretchr/testify/assert" - types "github.com/smartcontractkit/chainlink-common/pkg/types/automation" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types/automation" ) var validOutcome = AutomationOutcome{ - AgreedPerformables: []types.CheckResult{validConditionalResult, validLogResult}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, + AgreedPerformables: []commontypes.CheckResult{validConditionalResult, validLogResult}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, } var expectedEncodedOutcome []byte @@ -54,27 +55,35 @@ func TestAutomationOutcomeEncodeBackwardsCompatibility(t *testing.T) { func TestLargeAgreedPerformables(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, + AgreedPerformables: []commontypes.CheckResult{}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, } - for i := 0; i < OutcomeAgreedPerformablesLimit+1; i++ { - newConditionalResult := validConditionalResult - newConditionalResult.Trigger.BlockNumber = types.BlockNumber(i + 1) - newConditionalResult.WorkID = mockWorkIDGenerator(newConditionalResult.UpkeepID, newConditionalResult.Trigger) + size := 0 + for _, r := range ao.SurfacedProposals { + size += types.CoordinatedBlockProposalSize(len(r)) + } + for i := 0; size < MaxOutcomeLength; i++ { + newResult := validLogResult + uid := commontypes.UpkeepIdentifier{} + uid.FromBigInt(big.NewInt(int64(i + 10001))) + newResult.UpkeepID = uid + newResult.Trigger.BlockNumber = commontypes.BlockNumber(i + 1) + newResult.WorkID = mockWorkIDGenerator(newResult.UpkeepID, newResult.Trigger) ao.AgreedPerformables = append(ao.AgreedPerformables, validConditionalResult) + size += types.CheckResultSize(newResult) } encoded, err := ao.Encode() assert.NoError(t, err, "no error in encoding valid automation outcome") _, err = DecodeAutomationOutcome(encoded, mockUpkeepTypeGetter, mockWorkIDGenerator) assert.Error(t, err) - assert.ErrorContains(t, err, "outcome performable length cannot be greater than") + assert.ErrorContains(t, err, "outcome length cannot be greater than") } func TestDuplicateAgreedPerformables(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, + AgreedPerformables: []commontypes.CheckResult{}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{{validConditionalProposal, validLogProposal}}, } for i := 0; i < 2; i++ { ao.AgreedPerformables = append(ao.AgreedPerformables, validConditionalResult) @@ -89,16 +98,16 @@ func TestDuplicateAgreedPerformables(t *testing.T) { func TestLargeProposalHistory(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{validConditionalResult, validLogResult}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{}, + AgreedPerformables: []commontypes.CheckResult{validConditionalResult, validLogResult}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{}, } for i := 0; i < OutcomeSurfacedProposalsRoundHistoryLimit+1; i++ { newProposal := validConditionalProposal - uid := types.UpkeepIdentifier{} + uid := commontypes.UpkeepIdentifier{} uid.FromBigInt(big.NewInt(int64(i + 1))) newProposal.UpkeepID = uid newProposal.WorkID = mockWorkIDGenerator(newProposal.UpkeepID, newProposal.Trigger) - ao.SurfacedProposals = append(ao.SurfacedProposals, []types.CoordinatedBlockProposal{newProposal}) + ao.SurfacedProposals = append(ao.SurfacedProposals, []commontypes.CoordinatedBlockProposal{newProposal}) } encoded, err := ao.Encode() assert.NoError(t, err, "no error in encoding valid automation outcome") @@ -110,12 +119,12 @@ func TestLargeProposalHistory(t *testing.T) { func TestLargeSurfacedProposalInSingleRound(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{validConditionalResult, validLogResult}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{{}}, + AgreedPerformables: []commontypes.CheckResult{validConditionalResult, validLogResult}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{{}}, } for i := 0; i < OutcomeSurfacedProposalsLimit+1; i++ { newProposal := validConditionalProposal - uid := types.UpkeepIdentifier{} + uid := commontypes.UpkeepIdentifier{} uid.FromBigInt(big.NewInt(int64(i + 1))) newProposal.UpkeepID = uid newProposal.WorkID = mockWorkIDGenerator(newProposal.UpkeepID, newProposal.Trigger) @@ -131,11 +140,11 @@ func TestLargeSurfacedProposalInSingleRound(t *testing.T) { func TestDuplicateSurfaced(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{validConditionalResult, validLogResult}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{{}}, + AgreedPerformables: []commontypes.CheckResult{validConditionalResult, validLogResult}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{{}}, } for i := 0; i < 2; i++ { - ao.SurfacedProposals = append(ao.SurfacedProposals, []types.CoordinatedBlockProposal{validConditionalProposal}) + ao.SurfacedProposals = append(ao.SurfacedProposals, []commontypes.CoordinatedBlockProposal{validConditionalProposal}) } encoded, err := ao.Encode() assert.NoError(t, err, "no error in encoding valid automation outcome") @@ -147,13 +156,13 @@ func TestDuplicateSurfaced(t *testing.T) { func TestLargeOutcomeSize(t *testing.T) { ao := AutomationOutcome{ - AgreedPerformables: []types.CheckResult{}, - SurfacedProposals: [][]types.CoordinatedBlockProposal{}, + AgreedPerformables: []commontypes.CheckResult{}, + SurfacedProposals: [][]commontypes.CoordinatedBlockProposal{}, } largePerformData := [10001]byte{} for i := 0; i < OutcomeAgreedPerformablesLimit; i++ { newResult := validLogResult - uid := types.UpkeepIdentifier{} + uid := commontypes.UpkeepIdentifier{} uid.FromBigInt(big.NewInt(int64(i + 10001))) newResult.UpkeepID = uid newResult.WorkID = mockWorkIDGenerator(newResult.UpkeepID, newResult.Trigger) @@ -161,10 +170,10 @@ func TestLargeOutcomeSize(t *testing.T) { ao.AgreedPerformables = append(ao.AgreedPerformables, newResult) } for i := 0; i < OutcomeSurfacedProposalsRoundHistoryLimit; i++ { - round := []types.CoordinatedBlockProposal{} + round := []commontypes.CoordinatedBlockProposal{} for j := 0; j < OutcomeSurfacedProposalsLimit; j++ { newProposal := validLogProposal - uid := types.UpkeepIdentifier{} + uid := commontypes.UpkeepIdentifier{} uid.FromBigInt(big.NewInt(int64(i*OutcomeSurfacedProposalsLimit + j + 1001))) newProposal.UpkeepID = uid newProposal.WorkID = mockWorkIDGenerator(newProposal.UpkeepID, newProposal.Trigger) diff --git a/pkg/v3/plugin/hooks/add_from_staging.go b/pkg/v3/plugin/hooks/add_from_staging.go index 6655e016..49803b85 100644 --- a/pkg/v3/plugin/hooks/add_from_staging.go +++ b/pkg/v3/plugin/hooks/add_from_staging.go @@ -30,7 +30,7 @@ type AddFromStagingHook struct { // that is the same across all nodes for a given round. This ensures that all nodes try to // send the same subset of workIDs if they are available, while giving different priority // to workIDs in different rounds. -func (hook *AddFromStagingHook) RunHook(obs *ocr2keepersv3.AutomationObservation, limit int, rSrc [16]byte) error { +func (hook *AddFromStagingHook) RunHook(obs *ocr2keepersv3.AutomationObservation, sizeLimit int, rSrc [16]byte) error { results, err := hook.store.View() if err != nil { return err @@ -48,11 +48,24 @@ func (hook *AddFromStagingHook) RunHook(obs *ocr2keepersv3.AutomationObservation sort.Slice(results, func(i, j int) bool { return shuffledIDs[results[i].WorkID] < shuffledIDs[results[j].WorkID] }) - if len(results) > limit { - results = results[:limit] + + observationSize := types.BlockHistorySize(obs.BlockHistory) + observationSize += types.CoordinatedBlockProposalSize(len(obs.UpkeepProposals)) + for _, result := range obs.Performable { + observationSize += types.CheckResultSize(result) + } + // add results to observation until size limit is reached + added := 0 + for _, result := range results { + observationSize += types.CheckResultSize(result) + if observationSize > sizeLimit { + break + } + obs.Performable = append(obs.Performable, result) + added++ } - hook.logger.Printf("adding %d results to observation", len(results)) - obs.Performable = append(obs.Performable, results...) + + hook.logger.Printf("adding %d results to observation", added) return nil } diff --git a/pkg/v3/plugin/hooks/add_from_staging_test.go b/pkg/v3/plugin/hooks/add_from_staging_test.go index a24cfd9d..7c7376cf 100644 --- a/pkg/v3/plugin/hooks/add_from_staging_test.go +++ b/pkg/v3/plugin/hooks/add_from_staging_test.go @@ -10,17 +10,22 @@ import ( "github.com/stretchr/testify/mock" ocr2keepersv3 "github.com/smartcontractkit/chainlink-automation/pkg/v3" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" "github.com/smartcontractkit/chainlink-automation/pkg/v3/types/mocks" - types "github.com/smartcontractkit/chainlink-common/pkg/types/automation" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types/automation" ) +func limit(n int) int { + return types.CheckResultSize(commontypes.CheckResult{}) * n +} + func TestAddFromStagingHook_RunHook(t *testing.T) { tests := []struct { name string initialObservation ocr2keepersv3.AutomationObservation - resultStoreResults []types.CheckResult + resultStoreResults []commontypes.CheckResult resultStoreErr error - coordinatorFilterResults []types.CheckResult + coordinatorFilterResults []commontypes.CheckResult coordinatorErr error rSrc [16]byte limit int @@ -31,13 +36,13 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "Add results to observation", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, }, - limit: 10, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(10), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, @@ -48,22 +53,22 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "Empty result store", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{}, - coordinatorFilterResults: []types.CheckResult{}, - limit: 10, + resultStoreResults: []commontypes.CheckResult{}, + coordinatorFilterResults: []commontypes.CheckResult{}, + limit: limit(10), observationWorkIDs: []string{}, expectedLogMsg: "adding 0 results to observation", }, { name: "Filtered coordinator results observation", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, }, - limit: 10, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(10), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, }, @@ -73,14 +78,14 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "Existing results in observation appended", initialObservation: ocr2keepersv3.AutomationObservation{ - Performable: []types.CheckResult{{UpkeepID: [32]byte{3}, WorkID: "30a"}}, + Performable: []commontypes.CheckResult{{UpkeepID: [32]byte{3}, WorkID: "30a"}}, }, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, }, - limit: 10, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(10), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, }, @@ -90,13 +95,13 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "limits applied", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, }, - limit: 2, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(2), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, @@ -107,13 +112,13 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "limits applied in same order with same rSrc", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, }, - limit: 1, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(1), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, @@ -124,14 +129,14 @@ func TestAddFromStagingHook_RunHook(t *testing.T) { { name: "limits applied in different order with different rSrc", initialObservation: ocr2keepersv3.AutomationObservation{}, - resultStoreResults: []types.CheckResult{ + resultStoreResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, }, rSrc: [16]byte{1}, - limit: 2, - coordinatorFilterResults: []types.CheckResult{ + limit: limit(2), + coordinatorFilterResults: []commontypes.CheckResult{ {UpkeepID: [32]byte{1}, WorkID: "10c"}, {UpkeepID: [32]byte{2}, WorkID: "20b"}, {UpkeepID: [32]byte{3}, WorkID: "30a"}, @@ -196,13 +201,13 @@ func TestAddFromStagingHook_RunHook_Limits(t *testing.T) { { name: "limit is less than results", n: 1000, - limit: 100, + limit: types.CheckResultSize(commontypes.CheckResult{}) * 100, expected: 100, }, { name: "limit is greater than results", n: 100, - limit: 200, + limit: types.CheckResultSize(commontypes.CheckResult{}) * 1000, expected: 100, }, } @@ -236,9 +241,9 @@ func TestAddFromStagingHook_RunHook_Limits(t *testing.T) { } func getMocks(n int) (*mocks.MockResultStore, *mocks.MockCoordinator) { - mockResults := make([]types.CheckResult, n) + mockResults := make([]commontypes.CheckResult, n) for i := 0; i < n; i++ { - mockResults[i] = types.CheckResult{UpkeepID: [32]byte{uint8(i)}, WorkID: fmt.Sprintf("10%d", i)} + mockResults[i] = commontypes.CheckResult{UpkeepID: [32]byte{uint8(i)}, WorkID: fmt.Sprintf("10%d", i)} } mockResultStore := &mocks.MockResultStore{} mockResultStore.On("View").Return(mockResults, nil) diff --git a/pkg/v3/plugin/ocr3.go b/pkg/v3/plugin/ocr3.go index 2a61be29..a31066b3 100644 --- a/pkg/v3/plugin/ocr3.go +++ b/pkg/v3/plugin/ocr3.go @@ -62,15 +62,15 @@ func (plugin *ocr3Plugin) Observation(ctx context.Context, outctx ocr3types.Outc plugin.AddBlockHistoryHook.RunHook(&observation, ocr2keepersv3.ObservationBlockHistoryLimit) - if err := plugin.AddFromStagingHook.RunHook(&observation, ocr2keepersv3.ObservationPerformablesLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr)); err != nil { - return nil, err - } if err := plugin.AddLogProposalsHook.RunHook(&observation, ocr2keepersv3.ObservationLogRecoveryProposalsLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr)); err != nil { return nil, err } if err := plugin.AddConditionalProposalsHook.RunHook(&observation, ocr2keepersv3.ObservationConditionalsProposalsLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr)); err != nil { return nil, err } + if err := plugin.AddFromStagingHook.RunHook(&observation, ocr2keepersv3.MaxObservationLength, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr)); err != nil { + return nil, err + } plugin.Logger.Printf("built an observation in sequence nr %d with %d performables, %d upkeep proposals and %d block history", outctx.SeqNr, len(observation.Performable), len(observation.UpkeepProposals), len(observation.BlockHistory)) @@ -90,7 +90,9 @@ func (plugin *ocr3Plugin) ValidateObservation(outctx ocr3types.OutcomeContext, q func (plugin *ocr3Plugin) Outcome(outctx ocr3types.OutcomeContext, query ocr2plustypes.Query, attributedObservations []ocr2plustypes.AttributedObservation) (ocr3types.Outcome, error) { plugin.Logger.Printf("inside Outcome for seqNr %d", outctx.SeqNr) - p := newPerformables(plugin.F+1, ocr2keepersv3.OutcomeAgreedPerformablesLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr), plugin.Logger) + maxOutcomeProposals := ocr2keepersv3.OutcomeSurfacedProposalsLimit * ocr2keepersv3.OutcomeSurfacedProposalsRoundHistoryLimit + outcomePerformablesSizeLimit := ocr2keepersv3.MaxOutcomeLength - types.CoordinatedBlockProposalSize(maxOutcomeProposals) + p := newPerformables(plugin.F+1, outcomePerformablesSizeLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr), plugin.Logger) c := newCoordinatedBlockProposals(plugin.F+1, ocr2keepersv3.OutcomeSurfacedProposalsRoundHistoryLimit, ocr2keepersv3.OutcomeSurfacedProposalsLimit, getRandomKeySource(plugin.ConfigDigest, outctx.SeqNr), plugin.Logger) for _, attributedObservation := range attributedObservations { diff --git a/pkg/v3/plugin/ocr3_test.go b/pkg/v3/plugin/ocr3_test.go index 7c27dc45..7900f1b4 100644 --- a/pkg/v3/plugin/ocr3_test.go +++ b/pkg/v3/plugin/ocr3_test.go @@ -636,12 +636,14 @@ func TestOcr3Plugin_Observation(t *testing.T) { hash := crypto.Keccak256(append(uid[:], triggerExtBytes...)) return hex.EncodeToString(hash[:]) }, - RemoveFromStagingHook: hooks.NewRemoveFromStagingHook(resultStore, logger), - RemoveFromMetadataHook: hooks.NewRemoveFromMetadataHook(metadataStore, logger), - AddToProposalQHook: hooks.NewAddToProposalQHook(proposalQueue, logger), - AddBlockHistoryHook: hooks.NewAddBlockHistoryHook(metadataStore, logger), - AddFromStagingHook: hooks.NewAddFromStagingHook(resultStore, coordinator, logger), - Logger: logger, + RemoveFromStagingHook: hooks.NewRemoveFromStagingHook(resultStore, logger), + RemoveFromMetadataHook: hooks.NewRemoveFromMetadataHook(metadataStore, logger), + AddLogProposalsHook: hooks.NewAddLogProposalsHook(metadataStore, coordinator, logger), + AddConditionalProposalsHook: hooks.NewAddConditionalProposalsHook(metadataStore, coordinator, logger), + AddToProposalQHook: hooks.NewAddToProposalQHook(proposalQueue, logger), + AddBlockHistoryHook: hooks.NewAddBlockHistoryHook(metadataStore, logger), + AddFromStagingHook: hooks.NewAddFromStagingHook(resultStore, coordinator, logger), + Logger: logger, } previousOutcome := ocr2keepers2.AutomationOutcome{ diff --git a/pkg/v3/plugin/performable.go b/pkg/v3/plugin/performable.go index c17884ff..0228f5bc 100644 --- a/pkg/v3/plugin/performable.go +++ b/pkg/v3/plugin/performable.go @@ -6,6 +6,7 @@ import ( ocr2keepersv3 "github.com/smartcontractkit/chainlink-automation/pkg/v3" "github.com/smartcontractkit/chainlink-automation/pkg/v3/random" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" ocr2keepers "github.com/smartcontractkit/chainlink-common/pkg/types/automation" ) @@ -82,9 +83,15 @@ func (p *performables) set(outcome *ocr2keepersv3.AutomationOutcome) { return random.ShuffleString(performable[i].WorkID, p.keyRandSource) < random.ShuffleString(performable[j].WorkID, p.keyRandSource) }) - if len(performable) > p.limit { - p.logger.Printf("Limiting new performables in outcome to %d", p.limit) - performable = performable[:p.limit] + // adding performables until size limit is reached + size := 0 + for i, result := range performable { + size += types.CheckResultSize(result) + if p.limit < size { + p.logger.Printf("Limiting new performables in outcome to %d", i) + performable = performable[:i+1] + break + } } p.logger.Printf("Setting outcome.AgreedPerformables with %d performables", len(performable)) outcome.AgreedPerformables = performable diff --git a/pkg/v3/plugin/performable_test.go b/pkg/v3/plugin/performable_test.go index 20573250..829e31ee 100644 --- a/pkg/v3/plugin/performable_test.go +++ b/pkg/v3/plugin/performable_test.go @@ -9,51 +9,56 @@ import ( "github.com/stretchr/testify/assert" ocr2keepers "github.com/smartcontractkit/chainlink-automation/pkg/v3" - types "github.com/smartcontractkit/chainlink-common/pkg/types/automation" + "github.com/smartcontractkit/chainlink-automation/pkg/v3/types" + commontypes "github.com/smartcontractkit/chainlink-common/pkg/types/automation" ) +func limit(n int) int { + return types.CheckResultSize(commontypes.CheckResult{}) * n +} + func TestPerformables(t *testing.T) { tests := []struct { name string threshold int limit int observations []ocr2keepers.AutomationObservation - expectedOutcomeWorkIDs []types.CheckResult + expectedOutcomeWorkIDs []commontypes.CheckResult wantResultCount map[string]resultAndCount }{ { name: "No eligible results", threshold: 2, - limit: 3, + limit: limit(3), observations: []ocr2keepers.AutomationObservation{}, - expectedOutcomeWorkIDs: []types.CheckResult{}, + expectedOutcomeWorkIDs: []commontypes.CheckResult{}, wantResultCount: map[string]resultAndCount{}, }, { name: "No threshold met results", threshold: 2, - limit: 3, + limit: limit(3), observations: []ocr2keepers.AutomationObservation{ { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, }, - expectedOutcomeWorkIDs: []types.CheckResult{}, + expectedOutcomeWorkIDs: []commontypes.CheckResult{}, wantResultCount: map[string]resultAndCount{ "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909310909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -61,7 +66,7 @@ func TestPerformables(t *testing.T) { count: 1, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909320909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -69,7 +74,7 @@ func TestPerformables(t *testing.T) { count: 1, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909330909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -81,30 +86,30 @@ func TestPerformables(t *testing.T) { { name: "Duplicate work IDs increase the instance count", threshold: 2, - limit: 3, + limit: limit(5), observations: []ocr2keepers.AutomationObservation{ { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, }, - expectedOutcomeWorkIDs: []types.CheckResult{ + expectedOutcomeWorkIDs: []commontypes.CheckResult{ { WorkID: "2", FastGasWei: big.NewInt(10), @@ -113,7 +118,7 @@ func TestPerformables(t *testing.T) { }, wantResultCount: map[string]resultAndCount{ "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909310909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -121,7 +126,7 @@ func TestPerformables(t *testing.T) { count: 1, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909320909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -133,45 +138,45 @@ func TestPerformables(t *testing.T) { { name: "When the count exceeds the limit, the number of results are limited", threshold: 2, - limit: 1, + limit: limit(1) / 2, observations: []ocr2keepers.AutomationObservation{ { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, }, - expectedOutcomeWorkIDs: []types.CheckResult{ + expectedOutcomeWorkIDs: []commontypes.CheckResult{ { WorkID: "2", FastGasWei: big.NewInt(10), @@ -180,7 +185,7 @@ func TestPerformables(t *testing.T) { }, wantResultCount: map[string]resultAndCount{ "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909310909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -188,7 +193,7 @@ func TestPerformables(t *testing.T) { count: 1, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909320909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -196,7 +201,7 @@ func TestPerformables(t *testing.T) { count: 3, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909330909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -208,59 +213,59 @@ func TestPerformables(t *testing.T) { { name: "When the count exceeds the limit, the number of results are limited with same sorting order", threshold: 2, - limit: 1, + limit: limit(1) / 2, observations: []ocr2keepers.AutomationObservation{ { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, {WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, }, - expectedOutcomeWorkIDs: []types.CheckResult{ + expectedOutcomeWorkIDs: []commontypes.CheckResult{ { WorkID: "2", FastGasWei: big.NewInt(10), @@ -269,7 +274,7 @@ func TestPerformables(t *testing.T) { }, wantResultCount: map[string]resultAndCount{ "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909310909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -277,7 +282,7 @@ func TestPerformables(t *testing.T) { count: 1, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909320909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "2", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -285,7 +290,7 @@ func TestPerformables(t *testing.T) { count: 3, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909330909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "3", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -293,7 +298,7 @@ func TestPerformables(t *testing.T) { count: 3, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909340909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "4", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -301,7 +306,7 @@ func TestPerformables(t *testing.T) { count: 7, }, "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909350909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "5", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -313,32 +318,32 @@ func TestPerformables(t *testing.T) { { name: "Duplicate work IDs with different UIDs reaching threshold", threshold: 2, - limit: 3, + limit: limit(5), observations: []ocr2keepers.AutomationObservation{ { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10)}, }, }, { // Same workID but different fastGasWei - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(20), LinkNative: big.NewInt(10)}, }, }, { // Same workID but different fastGasWei - Performable: []types.CheckResult{ + Performable: []commontypes.CheckResult{ {WorkID: "1", FastGasWei: big.NewInt(20), LinkNative: big.NewInt(10)}, }, }, }, - expectedOutcomeWorkIDs: []types.CheckResult{ + expectedOutcomeWorkIDs: []commontypes.CheckResult{ { WorkID: "1", FastGasWei: big.NewInt(10), @@ -347,7 +352,7 @@ func TestPerformables(t *testing.T) { }, wantResultCount: map[string]resultAndCount{ "000966616c73650966616c73650900090000000000000000000000000000000000000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000000090909310909090a090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(10), LinkNative: big.NewInt(10), @@ -355,7 +360,7 @@ func TestPerformables(t *testing.T) { count: 2, }, "000966616c73650966616c736509000900000000000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000909093109090914090a09": { - result: types.CheckResult{ + result: commontypes.CheckResult{ WorkID: "1", FastGasWei: big.NewInt(20), LinkNative: big.NewInt(10), diff --git a/pkg/v3/types/len.go b/pkg/v3/types/len.go new file mode 100644 index 00000000..7588ba7c --- /dev/null +++ b/pkg/v3/types/len.go @@ -0,0 +1,82 @@ +package types + +import ( + "encoding/hex" + "encoding/json" + "math/big" + + "github.com/smartcontractkit/chainlink-common/pkg/types/automation" +) + +var ( + // approxCheckResultSize is the approximate size of a CheckResult in bytes, w/o performData. + // NOTE: will be filled in at init time + approxCheckResultSize = 0 + // approxCoordinatedBlockProposalSize is the approximate size of a CoordinatedBlockProposal in bytes. + // NOTE: will be filled in at init time + approxCoordinatedBlockProposalSize = 0 + // approxBlockKeySize is the approximate size of a BlockKey in bytes. + // NOTE: will be filled in at init time + approxBlockKeySize = 0 +) + +func init() { + bytes32 := [32]byte{} + maxUint64 := ^uint64(0) + maxUint32 := ^uint32(0) + maxUint8 := ^uint8(0) + workID := hex.EncodeToString(bytes32[:]) + uint256Max, _ := big.NewInt(0).SetString("115792089237316195423570985008687907853269984665640564039457584007913129639935", 10) + + trigger := automation.Trigger{ + BlockNumber: automation.BlockNumber(maxUint64), + BlockHash: bytes32, + LogTriggerExtension: &automation.LogTriggerExtension{ + TxHash: bytes32, + Index: maxUint32, + BlockHash: bytes32, + BlockNumber: automation.BlockNumber(maxUint64), + }, + } + approxCheckResultSize = approxObjectSize(automation.CheckResult{ + UpkeepID: bytes32, + PipelineExecutionState: maxUint8, + IneligibilityReason: maxUint8, + Trigger: trigger, + WorkID: workID, + GasAllocated: maxUint64, + FastGasWei: uint256Max, + LinkNative: uint256Max, + Eligible: true, + PerformData: []byte{}, + }) + approxCoordinatedBlockProposalSize = approxObjectSize(automation.CoordinatedBlockProposal{ + UpkeepID: bytes32, + Trigger: trigger, + WorkID: workID, + }) + approxBlockKeySize = approxObjectSize(automation.BlockKey{ + Number: automation.BlockNumber(maxUint64), + Hash: bytes32, + }) +} + +func approxObjectSize[O any](o O) int { + raw, err := json.Marshal(o) + if err != nil { + return 0 + } + return len(raw) +} + +func CheckResultSize(res automation.CheckResult) int { + return approxCheckResultSize + len(res.PerformData) +} + +func CoordinatedBlockProposalSize(n int) int { + return approxCoordinatedBlockProposalSize * n +} + +func BlockHistorySize(history automation.BlockHistory) int { + return approxBlockKeySize * len(history) +}