Skip to content

Commit

Permalink
Commit plugin parameter tuning (#296)
Browse files Browse the repository at this point in the history
* estimate max obs len

* parameter tuning

* parameter tuning

* fix linter err

* check query against max query len

* use offset range and resolve makram's comments
  • Loading branch information
dimkouv authored Nov 4, 2024
1 parent 4b7e196 commit e436c75
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 20 deletions.
46 changes: 39 additions & 7 deletions commit/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}
Expand Down
232 changes: 221 additions & 11 deletions commit/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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)}
}
Expand Down Expand Up @@ -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) {
Expand Down
8 changes: 6 additions & 2 deletions commit/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down

0 comments on commit e436c75

Please sign in to comment.