diff --git a/commit/factory.go b/commit/factory.go index 5bc1fc01a..72c61a591 100644 --- a/commit/factory.go +++ b/commit/factory.go @@ -27,9 +27,41 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pluginconfig" ) -// maxQueryLength is set to twice the maximum size of a theoretical merkle root processor query -// that assumes 1,000 source chains and 256 (theoretical max) RMN nodes. -const maxQueryLength = 615_520 +const ( + // Estimated maximum number of source chains the system will support. + // This value should be adjusted as we approach supporting that number of chains. + // Its primary purpose is to assist in defining the limits below. + estimatedMaxNumberOfSourceChains = 900 + + // Estimated maximum number of RMN nodes the system will support. + estimatedMaxRmnNodesCount = 256 + + // Estimated maximum number of priced tokens that the Commit DON supports. + // This value does not indicate a system limitation but just an estimation to properly tune the OCR parameters. + // The value can be adjusted as needed. + estimatedMaxNumberOfPricedTokens = 10_000 + + // maxQueryLength is set to twice the maximum size of a theoretical merkle root processor query + // that assumes estimatedMaxNumberOfSourceChains source chains and + // estimatedMaxRmnNodesCount (theoretical max) RMN nodes. + // check factory_test for the calculation + maxQueryLength = 559_320 + + // maxObservationLength is set to the maximum size of an observation + // check factory_test for the calculation + maxObservationLength = 1_047_202 + + // maxOutcomeLength is set to the maximum size of an outcome + // check factory_test for the calculation + maxOutcomeLength = 1_167_765 + + // maxReportLength is set to an estimate of a maximum report size + // check factory_test for the calculation + maxReportLength = 993_982 + + // maxReportCount is set to 1 because the commit plugin only generates one report per round. + maxReportCount = 1 +) // PluginFactoryConstructor implements common OCR3ReportingPluginClient and is used for initializing a plugin factory // and a validation service. @@ -209,10 +241,10 @@ func (p *PluginFactory) NewReportingPlugin(ctx context.Context, config ocr3types Name: "CCIPRoleCommit", Limits: ocr3types.ReportingPluginLimits{ MaxQueryLength: maxQueryLength, - MaxObservationLength: 20_000, // 20kB - MaxOutcomeLength: 10_000, // 10kB - MaxReportLength: 10_000, // 10kB - MaxReportCount: 10, + MaxObservationLength: maxObservationLength, + MaxOutcomeLength: maxOutcomeLength, + MaxReportLength: maxReportLength, + MaxReportCount: maxReportCount, }, }, nil } diff --git a/commit/factory_test.go b/commit/factory_test.go index 4961f406f..2a333d311 100644 --- a/commit/factory_test.go +++ b/commit/factory_test.go @@ -3,10 +3,13 @@ package commit import ( "encoding/json" "math" + "strings" "testing" + "time" "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/merklemulti" + "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" "github.com/stretchr/testify/assert" @@ -16,25 +19,20 @@ import ( "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot" "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn" "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/rmnpb" + rmntypes "github.com/smartcontractkit/chainlink-ccip/commit/merkleroot/rmn/types" "github.com/smartcontractkit/chainlink-ccip/commit/tokenprice" + dt "github.com/smartcontractkit/chainlink-ccip/internal/plugincommon/discovery/discoverytypes" + "github.com/smartcontractkit/chainlink-ccip/internal/plugintypes" reader2 "github.com/smartcontractkit/chainlink-ccip/internal/reader" "github.com/smartcontractkit/chainlink-ccip/pkg/reader" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" "github.com/smartcontractkit/chainlink-ccip/pluginconfig" ) func Test_maxQueryLength(t *testing.T) { // This test will verify that the maxQueryLength constant is set to a proper value. - // Estimate the maximum number of source chains we are going to ever have. - // This value should be tweaked after we are close to supporting that many chains. - const estimatedMaxNumberOfSourceChains = 1000 - - // Estimate the maximum number of RMN report signers we are going to ever have. - // This value is defined in RMNRemote contract as `f`. - // This value should be tweaked if necessary in order to define new limits. - const estimatedMaxRmnReportSigners = 256 - - sigs := make([]*rmnpb.EcdsaSignature, estimatedMaxRmnReportSigners) + sigs := make([]*rmnpb.EcdsaSignature, estimatedMaxRmnNodesCount) for i := range sigs { sigs[i] = &rmnpb.EcdsaSignature{R: make([]byte, 32), S: make([]byte, 32)} } @@ -69,7 +67,219 @@ func Test_maxQueryLength(t *testing.T) { require.NoError(t, err) // We set twice the size, for extra safety while making breaking changes between oracle versions. - assert.Equal(t, 2*len(b), maxQueryLength) + const testOffset = 10 + assert.Greater(t, maxQueryLength, 2*len(b)-testOffset) + assert.Less(t, maxQueryLength, 2*len(b)+testOffset) + require.Less(t, maxQueryLength, ocr3types.MaxMaxQueryLength) +} + +func Test_maxObservationLength(t *testing.T) { + const maxContractsPerChain = 6 // router/onramp/offramp/rmnHome/rmnRemote/priceRegistry + + merkleRootObs := merkleroot.Observation{ + MerkleRoots: make([]ccipocr3.MerkleRootChain, estimatedMaxNumberOfSourceChains), + OnRampMaxSeqNums: make([]plugintypes.SeqNumChain, estimatedMaxNumberOfSourceChains), + OffRampNextSeqNums: make([]plugintypes.SeqNumChain, estimatedMaxNumberOfSourceChains), + RMNRemoteConfig: rmntypes.RemoteConfig{ + ContractAddress: make([]byte, 20), + ConfigDigest: [32]byte{}, + Signers: make([]rmntypes.RemoteSignerInfo, estimatedMaxRmnNodesCount), + F: math.MaxUint64, + ConfigVersion: math.MaxUint32, + RmnReportVersion: [32]byte{}, + }, + FChain: make(map[ccipocr3.ChainSelector]int, estimatedMaxNumberOfSourceChains), + } + + for i := range merkleRootObs.MerkleRoots { + merkleRootObs.MerkleRoots[i] = ccipocr3.MerkleRootChain{ + ChainSel: math.MaxUint64, + OnRampAddress: make([]byte, 40), + SeqNumsRange: ccipocr3.NewSeqNumRange(math.MaxUint64, math.MaxUint64), + MerkleRoot: [32]byte{}, + } + } + + for i := range merkleRootObs.OnRampMaxSeqNums { + merkleRootObs.OnRampMaxSeqNums[i] = plugintypes.SeqNumChain{ + ChainSel: math.MaxUint64, + SeqNum: math.MaxUint64, + } + } + + for i := range merkleRootObs.OffRampNextSeqNums { + merkleRootObs.OffRampNextSeqNums[i] = plugintypes.SeqNumChain{ + ChainSel: math.MaxUint64, + SeqNum: math.MaxUint64, + } + } + + for i := range merkleRootObs.RMNRemoteConfig.Signers { + merkleRootObs.RMNRemoteConfig.Signers[i] = rmntypes.RemoteSignerInfo{ + OnchainPublicKey: make([]byte, 40), + NodeIndex: math.MaxUint64, + } + } + + maxObs := Observation{ + MerkleRootObs: merkleRootObs, + TokenPriceObs: tokenprice.Observation{ + FeedTokenPrices: make([]ccipocr3.TokenPrice, estimatedMaxNumberOfPricedTokens), + FeeQuoterTokenUpdates: make(map[ccipocr3.UnknownEncodedAddress]plugintypes.TimestampedBig, + estimatedMaxNumberOfPricedTokens), + FChain: make(map[ccipocr3.ChainSelector]int, estimatedMaxNumberOfSourceChains), + Timestamp: time.Now(), + }, + ChainFeeObs: chainfee.Observation{ + FeeComponents: make(map[ccipocr3.ChainSelector]types.ChainFeeComponents, estimatedMaxNumberOfSourceChains), + NativeTokenPrices: make(map[ccipocr3.ChainSelector]ccipocr3.BigInt, estimatedMaxNumberOfPricedTokens), + ChainFeeUpdates: make(map[ccipocr3.ChainSelector]chainfee.Update, estimatedMaxNumberOfSourceChains), + FChain: make(map[ccipocr3.ChainSelector]int, estimatedMaxNumberOfSourceChains), + TimestampNow: time.Now(), + }, + DiscoveryObs: dt.Observation{ + FChain: make(map[ccipocr3.ChainSelector]int, estimatedMaxNumberOfSourceChains), + Addresses: make(reader.ContractAddresses, estimatedMaxNumberOfSourceChains*maxContractsPerChain), + }, + FChain: make(map[ccipocr3.ChainSelector]int, estimatedMaxNumberOfSourceChains), + } + + for i := range maxObs.TokenPriceObs.FeedTokenPrices { + maxObs.TokenPriceObs.FeedTokenPrices[i] = ccipocr3.TokenPrice{ + TokenID: ccipocr3.UnknownEncodedAddress(strings.Repeat("x", 20)), + Price: ccipocr3.NewBigIntFromInt64(math.MaxInt64), + } + } + + b, err := maxObs.Encode() + require.NoError(t, err) + + const testOffset = 50 + assert.Greater(t, maxObservationLength, len(b)-testOffset) + assert.Less(t, maxObservationLength, len(b)+testOffset) + assert.Less(t, maxObservationLength, ocr3types.MaxMaxObservationLength) +} + +func Test_maxOutcomeLength(t *testing.T) { + maxOutc := Outcome{ + MerkleRootOutcome: merkleroot.Outcome{ + OutcomeType: merkleroot.OutcomeType(math.MaxInt), + RangesSelectedForReport: make([]plugintypes.ChainRange, estimatedMaxNumberOfSourceChains), + RootsToReport: make([]ccipocr3.MerkleRootChain, estimatedMaxNumberOfSourceChains), + OffRampNextSeqNums: make([]plugintypes.SeqNumChain, estimatedMaxNumberOfSourceChains), + ReportTransmissionCheckAttempts: math.MaxUint64, + RMNReportSignatures: make([]ccipocr3.RMNECDSASignature, estimatedMaxRmnNodesCount), + RMNRemoteCfg: rmntypes.RemoteConfig{ + ContractAddress: make([]byte, 20), + ConfigDigest: [32]byte{}, + Signers: make([]rmntypes.RemoteSignerInfo, estimatedMaxRmnNodesCount), + F: math.MaxUint64, + ConfigVersion: math.MaxUint32, + RmnReportVersion: [32]byte{}, + }, + }, + TokenPriceOutcome: tokenprice.Outcome{ + TokenPrices: make([]ccipocr3.TokenPrice, estimatedMaxNumberOfPricedTokens), + }, + ChainFeeOutcome: chainfee.Outcome{ + GasPrices: make([]ccipocr3.GasPriceChain, estimatedMaxNumberOfSourceChains), + }, + } + + for i := range maxOutc.MerkleRootOutcome.RangesSelectedForReport { + maxOutc.MerkleRootOutcome.RangesSelectedForReport[i] = plugintypes.ChainRange{ + ChainSel: math.MaxUint64, + SeqNumRange: ccipocr3.NewSeqNumRange(math.MaxUint64, math.MaxUint64), + } + } + + for i := range maxOutc.MerkleRootOutcome.RootsToReport { + maxOutc.MerkleRootOutcome.RootsToReport[i] = ccipocr3.MerkleRootChain{ + ChainSel: math.MaxUint64, + OnRampAddress: make([]byte, 40), + SeqNumsRange: ccipocr3.NewSeqNumRange(math.MaxUint64, math.MaxUint64), + MerkleRoot: [32]byte{}, + } + } + + for i := range maxOutc.MerkleRootOutcome.OffRampNextSeqNums { + maxOutc.MerkleRootOutcome.OffRampNextSeqNums[i] = plugintypes.SeqNumChain{ + ChainSel: math.MaxUint64, + SeqNum: math.MaxUint64, + } + } + + for i := range maxOutc.MerkleRootOutcome.RMNRemoteCfg.Signers { + maxOutc.MerkleRootOutcome.RMNRemoteCfg.Signers[i] = rmntypes.RemoteSignerInfo{ + OnchainPublicKey: make([]byte, 40), + NodeIndex: math.MaxUint64, + } + } + + for i := range maxOutc.TokenPriceOutcome.TokenPrices { + maxOutc.TokenPriceOutcome.TokenPrices[i] = ccipocr3.TokenPrice{ + TokenID: ccipocr3.UnknownEncodedAddress(strings.Repeat("x", 20)), + Price: ccipocr3.NewBigIntFromInt64(math.MaxInt64), + } + } + + for i := range maxOutc.ChainFeeOutcome.GasPrices { + maxOutc.ChainFeeOutcome.GasPrices[i] = ccipocr3.GasPriceChain{ + ChainSel: math.MaxUint64, + GasPrice: ccipocr3.NewBigIntFromInt64(math.MaxInt64), + } + } + + b, err := maxOutc.Encode() + require.NoError(t, err) + + const testOffset = 10 + assert.Greater(t, maxOutcomeLength, len(b)-testOffset) + assert.Less(t, maxOutcomeLength, len(b)+testOffset) + assert.Less(t, maxOutcomeLength, ocr3types.MaxMaxOutcomeLength) +} + +func Test_maxReportLength(t *testing.T) { + rep := ccipocr3.CommitPluginReport{ + MerkleRoots: make([]ccipocr3.MerkleRootChain, estimatedMaxNumberOfSourceChains), + PriceUpdates: ccipocr3.PriceUpdates{ + TokenPriceUpdates: make([]ccipocr3.TokenPrice, estimatedMaxNumberOfPricedTokens), + GasPriceUpdates: make([]ccipocr3.GasPriceChain, estimatedMaxNumberOfSourceChains), + }, + RMNSignatures: make([]ccipocr3.RMNECDSASignature, estimatedMaxRmnNodesCount), + } + + for i := range rep.MerkleRoots { + rep.MerkleRoots[i] = ccipocr3.MerkleRootChain{ + ChainSel: math.MaxUint64, + OnRampAddress: make([]byte, 40), + SeqNumsRange: ccipocr3.NewSeqNumRange(math.MaxUint64, math.MaxUint64), + MerkleRoot: [32]byte{}, + } + } + + for i := range rep.PriceUpdates.TokenPriceUpdates { + rep.PriceUpdates.TokenPriceUpdates[i] = ccipocr3.TokenPrice{ + TokenID: ccipocr3.UnknownEncodedAddress(strings.Repeat("x", 20)), + Price: ccipocr3.NewBigIntFromInt64(math.MaxInt64), + } + } + + for i := range rep.PriceUpdates.GasPriceUpdates { + rep.PriceUpdates.GasPriceUpdates[i] = ccipocr3.GasPriceChain{ + ChainSel: math.MaxUint64, + GasPrice: ccipocr3.NewBigIntFromInt64(math.MaxInt64), + } + } + + // Chain specific encoding are more compact than JSON. We measure using JSON encoding. + b, err := json.Marshal(rep) + require.NoError(t, err) + + const testOffset = 10 + assert.Greater(t, maxReportLength, len(b)-testOffset) + assert.Less(t, maxReportLength, len(b)+testOffset) + assert.Less(t, maxReportLength, ocr3types.MaxMaxReportLength) } func TestPluginFactory_NewReportingPlugin(t *testing.T) { diff --git a/commit/report.go b/commit/report.go index 7586ddf21..fc363c70c 100644 --- a/commit/report.go +++ b/commit/report.go @@ -85,8 +85,12 @@ func (p *Plugin) Reports( } return []ocr3types.ReportPlus[[]byte]{ - {ReportWithInfo: ocr3types.ReportWithInfo[[]byte]{ - Report: encodedReport, Info: infoBytes}}, + { + ReportWithInfo: ocr3types.ReportWithInfo[[]byte]{ + Report: encodedReport, + Info: infoBytes, + }, + }, }, nil }