diff --git a/build/devenv/cciptestinterfaces/interface.go b/build/devenv/cciptestinterfaces/interface.go index b5bb1a6cd..73fe86e74 100644 --- a/build/devenv/cciptestinterfaces/interface.go +++ b/build/devenv/cciptestinterfaces/interface.go @@ -151,10 +151,14 @@ type Chain interface { GetRoundRobinUser() func() *bind.TransactOpts } +type Signers struct { + Signers [][]byte + Threshold uint8 +} + type OnChainCommittees struct { - CommitteeQualifier string - Signers [][]byte - Threshold uint8 + CommitteeQualifier string + SignersBySourceChain map[uint64]Signers } // OnChainConfigurable defines methods that allows devenv to diff --git a/build/devenv/env.toml b/build/devenv/env.toml index 3e3ae4750..6d4d09c69 100644 --- a/build/devenv/env.toml +++ b/build/devenv/env.toml @@ -106,6 +106,7 @@ cl_nodes_funding_link = 50 # Tertiary Verifier Setup +# Tertiary verifier secures/enables chain 1337 with 2/3, chain 2337 with 1/2, and chain 3337 with 1/1 [[verifier]] image = "verifier:dev" container_name = "tertiary-verifier-1" @@ -114,6 +115,7 @@ cl_nodes_funding_link = 50 root_path = "../../" committee_name = "tertiary" node_index = 0 + enabled_chains = ["1337", "2337"] [verifier.db] image = "postgres:16-alpine" name = "tertiary-verifier-1-db" @@ -128,12 +130,27 @@ cl_nodes_funding_link = 50 root_path = "../../" committee_name = "tertiary" node_index = 1 + enabled_chains = ["1337", "2337"] [verifier.db] image = "postgres:16-alpine" name = "tertiary-verifier-2-db" port = 8437 +[[verifier]] + image = "verifier:dev" + container_name = "tertiary-verifier-3" + port = 8650 + source_code_path = "../verifier" + root_path = "../../" + committee_name = "tertiary" + node_index = 2 + enabled_chains = ["1337", "3337"] + [verifier.db] + image = "postgres:16-alpine" + name = "tertiary-verifier-3-db" + port = 8438 + [[token_verifier]] mode = "standalone" image = "token-verifier:dev" @@ -142,7 +159,6 @@ cl_nodes_funding_link = 50 source_code_path = "../verifier" root_path = "../../" - [[executor]] image = "executor:dev" port = 8101 @@ -368,6 +384,12 @@ URI = "postgresql://indexer:indexer@indexer-db:5432/indexer?sslmode=disable" source_code_path = "../aggregator" root_path = "../../" + # tertiary verifier is composed of a 3 committee verifiers and the 3 chains are secured with different thresholds + [aggregator.threshold_per_source] + 1337 = 2 + 2337 = 1 + 3337 = 1 + [aggregator.db] image = "postgres:16-alpine" host_port = 7434 @@ -388,6 +410,12 @@ URI = "postgresql://indexer:indexer@indexer-db:5432/indexer?sslmode=disable" enabled = true groups = ["verifiers"] + [[aggregator.api_clients]] + client_id = "tertiary-verifier-3" + description = "Development tertiary verifier 3" + enabled = true + groups = ["verifiers"] + [[aggregator.api_clients]] client_id = "monitoring" description = "Monitoring and infrastructure client" @@ -399,7 +427,7 @@ URI = "postgresql://indexer:indexer@indexer-db:5432/indexer?sslmode=disable" description = "Development Indexer" enabled = true groups = ["indexer"] - # Environment variables for sensitive configuration + [aggregator.env] storage_connection_url = "postgresql://aggregator:aggregator@tertiary-aggregator-db:5432/aggregator?sslmode=disable" redis_address = "tertiary-aggregator-redis:6379" diff --git a/build/devenv/environment.go b/build/devenv/environment.go index d38eb818c..bb59957ff 100644 --- a/build/devenv/environment.go +++ b/build/devenv/environment.go @@ -9,6 +9,7 @@ import ( "net/http" "os" "path/filepath" + "slices" "strconv" "strings" @@ -317,25 +318,6 @@ func NewEnvironment() (in *Cfg, err error) { ///////////////////////////// // START: Deploy contracts // ///////////////////////////// - var committees []cciptestinterfaces.OnChainCommittees - { - addrs := make(map[string][][]byte) - - for _, ver := range in.Verifier { - // At this point, SigningKeyPublic must be assigned -- either by keygen either manually or by the CL node. - addrs[ver.CommitteeName] = append(addrs[ver.CommitteeName], hexutil.MustDecode(ver.SigningKeyPublic)) - } - - for committeeName, signers := range addrs { - committees = append(committees, cciptestinterfaces.OnChainCommittees{ - CommitteeQualifier: committeeName, - Signers: signers, - // TODO: should this be pulled from the aggregator configuration, ThresholdPerSource? - // And if nothing in there, default to len(signers)? - Threshold: uint8(len(signers)), - }) - } - } var selectors []uint64 var e *deployment.Environment @@ -348,6 +330,77 @@ func NewEnvironment() (in *Cfg, err error) { } L.Info().Any("Selectors", selectors).Msg("Deploying for chain selectors") + // store one entry per committee, with signers per source chain + var committees []cciptestinterfaces.OnChainCommittees + { + // Map aggregator inputs by committee name for threshold lookup + aggByCommittee := make(map[string]*services.AggregatorInput) + for _, agg := range in.Aggregator { + aggByCommittee[agg.CommitteeName] = agg + } + + // Collect unique committee names from verifiers + committeeSet := make(map[string]bool) + for _, ver := range in.Verifier { + committeeSet[ver.CommitteeName] = true + } + + // For each committee, build an OnChainCommittees entry with signers per source chain + for committeeName := range committeeSet { + signersBySourceChain := make(map[uint64]cciptestinterfaces.Signers) + + for _, selector := range selectors { + // Determine signers for this committee on this selector + signers := make([][]byte, 0) + chainIDStr, err := chainsel.GetChainIDFromSelector(selector) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID from selector %d: %w", selector, err) + } + + for _, ver := range in.Verifier { + if ver.CommitteeName != committeeName { + continue + } + // If EnabledChains is empty, verifier secures all chains. + if len(ver.EnabledChains) == 0 { + signers = append(signers, hexutil.MustDecode(ver.SigningKeyPublic)) + continue + } + // Otherwise include only if this verifier enabled this chain (compare chain ID strings) + if slices.Contains(ver.EnabledChains, chainIDStr) { + signers = append(signers, hexutil.MustDecode(ver.SigningKeyPublic)) + } + } + + // Determine threshold for this committee+source chain + var threshold uint8 + agg := aggByCommittee[committeeName] + if agg != nil && agg.ThresholdPerSource != nil { + // ThresholdPerSource is keyed by chain id string + if t, ok := agg.ThresholdPerSource[chainIDStr]; ok { + threshold = t + } else { + // If threshold map is present but this source is missing, skip this entry (aggregator ignores it) + continue + } + } else { + // default: full quorum (all signers for that chain) + threshold = uint8(len(signers)) + } + + signersBySourceChain[selector] = cciptestinterfaces.Signers{ + Signers: signers, + Threshold: threshold, + } + } + + committees = append(committees, cciptestinterfaces.OnChainCommittees{ + CommitteeQualifier: committeeName, + SignersBySourceChain: signersBySourceChain, + }) + } + } + timeTrack.Record("[infra] deploying blockchains") ds := datastore.NewMemoryDataStore() for i, impl := range impls { diff --git a/build/devenv/evm/impl.go b/build/devenv/evm/impl.go index 8cce818c3..336e3c6f4 100644 --- a/build/devenv/evm/impl.go +++ b/build/devenv/evm/impl.go @@ -88,6 +88,8 @@ const ( QuaternaryReceiverQualifier = "quaternary" + QuinaryReceiverQualifier = "quinary" + CustomExecutorQualifier = "custom" CommitteeVerifierGasForVerification = 500_000 @@ -1089,6 +1091,15 @@ func toCommitteeVerifierParams(committees []cciptestinterfaces.OnChainCommittees return params } +// convert signer public key byte array to the EVM address string representations. +func signersBytesToStrings(bs [][]byte) []string { + s := make([]string, 0, len(bs)) + for _, b := range bs { + s = append(s, common.BytesToAddress(b).String()) + } + return s +} + func (m *CCIP17EVMConfig) DeployContractsForSelector(ctx context.Context, env *deployment.Environment, selector uint64, committees []cciptestinterfaces.OnChainCommittees) (datastore.DataStore, error) { l := m.logger l.Info().Msg("Configuring contracts for selector") @@ -1256,6 +1267,20 @@ func (m *CCIP17EVMConfig) DeployContractsForSelector(ctx context.Context, env *d OptionalThreshold: 1, Qualifier: QuaternaryReceiverQualifier, }, + { + // single required verifier (tertiary), no optional verifiers, no optional threshold + // used to test NOP downsizing e2e + Version: semver.MustParse(mock_receiver.Deploy.Version()), + RequiredVerifiers: []datastore.AddressRef{ + { + Type: datastore.ContractType(committee_verifier.ResolverType), + Version: semver.MustParse(committee_verifier.Deploy.Version()), + ChainSelector: selector, + Qualifier: TertiaryCommitteeVerifierQualifier, + }, + }, + Qualifier: QuinaryReceiverQualifier, + }, }, }, }, @@ -1456,20 +1481,27 @@ func (m *CCIP17EVMConfig) configureTokenForTransfer( return nil } -func toComitteeVerifier(selector uint64, committees []cciptestinterfaces.OnChainCommittees, remoteChainConfigs map[uint64]adapters.CommitteeVerifierRemoteChainConfig) []adapters.CommitteeVerifierConfig[datastore.AddressRef] { +func toCommitteeVerifier(selector uint64, committees []cciptestinterfaces.OnChainCommittees, remoteChainConfigs map[uint64]adapters.CommitteeVerifierRemoteChainConfig) []adapters.CommitteeVerifierConfig[datastore.AddressRef] { + // chainIDStr, _ := chainsel.GetChainIDFromSelector(selector) + // fmt.Printf("toCommitteeVerifier: creating committee verifiers for selector %d/%s\n", selector, chainIDStr) committeeVerifiers := make([]adapters.CommitteeVerifierConfig[datastore.AddressRef], 0, len(committees)) for _, committee := range committees { remoteChainConfigWithSignatureConfig := make(map[uint64]adapters.CommitteeVerifierRemoteChainConfig, len(remoteChainConfigs)) for remoteChainSelector, remoteChainConfig := range remoteChainConfigs { - signers := make([]string, 0, len(committee.Signers)) - for _, signer := range committee.Signers { - signers = append(signers, common.BytesToAddress(signer).String()) + // Check if this committee has signers for this source chain + signerData, ok := committee.SignersBySourceChain[remoteChainSelector] + if !ok { + continue } + signers := signersBytesToStrings(signerData.Signers) remoteChainConfig.SignatureConfig = adapters.CommitteeVerifierSignatureQuorumConfig{ - Threshold: committee.Threshold, + Threshold: signerData.Threshold, Signers: signers, } remoteChainConfigWithSignatureConfig[remoteChainSelector] = remoteChainConfig + // remoteChainIDStr, _ := chainsel.GetChainIDFromSelector(remoteChainSelector) + // fmt.Printf("committeeVerifiers[%s]: for source chain %d/%s -> %+v\n", committee.CommitteeQualifier, remoteChainSelector, remoteChainIDStr, remoteChainConfig.SignatureConfig) + // TODO set PayloadSizeBytes calculated from signers list } committeeVerifiers = append(committeeVerifiers, adapters.CommitteeVerifierConfig[datastore.AddressRef]{ CommitteeVerifier: []datastore.AddressRef{ @@ -1489,6 +1521,9 @@ func toComitteeVerifier(selector uint64, committees []cciptestinterfaces.OnChain RemoteChains: remoteChainConfigWithSignatureConfig, }) } + + // fmt.Printf("committeeVerifiers:\n%+v\n", committeeVerifiers) + return committeeVerifiers } @@ -1575,8 +1610,8 @@ func (m *CCIP17EVMConfig) ConnectContractsWithSelectors(ctx context.Context, e * committeeVerifierRemoteChainConfigs[rs] = adapters.CommitteeVerifierRemoteChainConfig{ AllowlistEnabled: false, GasForVerification: CommitteeVerifierGasForVerification, - FeeUSDCents: 0, // TODO: set proper fee - PayloadSizeBytes: 0, // TODO: set proper payload size + FeeUSDCents: 50, // Copied from CLD/deploy_ccip.go + PayloadSizeBytes: 0, // will be set later after we gather the # of signatures } } @@ -1604,7 +1639,7 @@ func (m *CCIP17EVMConfig) ConnectContractsWithSelectors(ctx context.Context, e * Type: datastore.ContractType(routeroperations.ContractType), Version: semver.MustParse(routeroperations.Deploy.Version()), }, - CommitteeVerifiers: toComitteeVerifier(selector, committees, committeeVerifierRemoteChainConfigs), + CommitteeVerifiers: toCommitteeVerifier(selector, committees, committeeVerifierRemoteChainConfigs), }, }, }) diff --git a/build/devenv/gencfg/gencfg.go b/build/devenv/gencfg/gencfg.go index 6580fadd8..781a903c0 100644 --- a/build/devenv/gencfg/gencfg.go +++ b/build/devenv/gencfg/gencfg.go @@ -14,6 +14,7 @@ import ( "golang.org/x/oauth2" "gopkg.in/yaml.v2" + chainsel "github.com/smartcontractkit/chain-selectors" "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/committee_verifier" executor_operations "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/executor" offrampoperations "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/offramp" @@ -81,11 +82,12 @@ func GenerateConfigs(cldDomain string, verifierPubKeys []string, numExecutors in rmnRemoteAddresses = make(map[string]string) rmnRemoteAddressesUint64 = make(map[uint64]string) offRampAddresses = make(map[uint64]string) - thresholdPerSource = make(map[uint64]uint8) + thresholdPerSource = make(map[string]uint8) ) for _, ref := range addressRefs { chainSelectorStr := strconv.FormatUint(ref.ChainSelector, 10) + chainID, _ := chainsel.GetChainIDFromSelector(ref.ChainSelector) switch ref.Type { case datastore.ContractType(onrampoperations.ContractType): onRampAddresses[chainSelectorStr] = ref.Address @@ -101,7 +103,7 @@ func GenerateConfigs(cldDomain string, verifierPubKeys []string, numExecutors in case datastore.ContractType(offrampoperations.ContractType): offRampAddresses[ref.ChainSelector] = ref.Address } - thresholdPerSource[ref.ChainSelector] = ocrThreshold(len(verifierPubKeys)) + thresholdPerSource[chainID] = ocrThreshold(len(verifierPubKeys)) } tempDir, err := os.MkdirTemp("", "ccv-configs") diff --git a/build/devenv/services/aggregator.go b/build/devenv/services/aggregator.go index 544691c51..9d2b3078a 100644 --- a/build/devenv/services/aggregator.go +++ b/build/devenv/services/aggregator.go @@ -15,6 +15,7 @@ import ( "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" + chainsel "github.com/smartcontractkit/chain-selectors" aggregator "github.com/smartcontractkit/chainlink-ccv/aggregator/pkg" "github.com/smartcontractkit/chainlink-ccv/aggregator/pkg/configuration" "github.com/smartcontractkit/chainlink-ccv/aggregator/pkg/model" @@ -102,8 +103,9 @@ type AggregatorInput struct { // Chain selector -> Committee Verifier Resolver Address CommitteeVerifierResolverAddresses map[uint64]string `toml:"committee_verifier_resolver_addresses"` // Source chain selector -> threshold mapping - // If not available we default to a full quorum, i.e. all verifiers must sign. - ThresholdPerSource map[uint64]uint8 `toml:"threshold_per_source"` + // Accept TOML map with string keys (stringified chain selectors) + ThresholdPerSource map[string]uint8 `toml:"threshold_per_source"` + // Maps to Monitoring.Beholder.OtelExporterHTTPEndpoint in the aggregator config toml. MonitoringOtelExporterHTTPEndpoint string `toml:"monitoring_otel_exporter_http_endpoint"` @@ -244,17 +246,36 @@ func (a *AggregatorInput) GenerateConfigs(inV []*VerifierInput, generatedConfigF committeeConfig.QuorumConfigs = make(map[string]*model.QuorumConfig) committeeConfig.DestinationVerifiers = make(map[string]string) - // Build signers list from verifier inputs - signers := make([]model.Signer, 0, len(inV)) + // Build signers map: chainSelector -> []model.Signer + signersPerChain := make(map[uint64][]model.Signer) + + // Collect all source chain selectors + allChainSelectors := make([]uint64, 0, len(a.CommitteeVerifierResolverAddresses)) + for cs := range a.CommitteeVerifierResolverAddresses { + allChainSelectors = append(allChainSelectors, cs) + } + for _, v := range inV { if v.CommitteeName != committeeName { continue } - signers = append(signers, model.Signer{ - Address: v.SigningKeyPublic, - }) + // each verifier has an optional secures optional with the list of chain selectors it secures. + // If EnabledChains is nil, it secures all chains. + signer := model.Signer{Address: v.SigningKeyPublic} + if v.EnabledChains == nil { + for _, chainSelector := range allChainSelectors { + signersPerChain[chainSelector] = append(signersPerChain[chainSelector], signer) + } + } else { + for _, chainID := range v.EnabledChains { + networkInfo, err := chainsel.GetChainDetailsByChainIDAndFamily(chainID, chainsel.FamilyEVM) + if err != nil { + return nil, fmt.Errorf("failed to get chain details for chain ID %s: %w", chainID, err) + } + signersPerChain[networkInfo.ChainSelector] = append(signersPerChain[networkInfo.ChainSelector], signer) + } + } } - defaultThreshold := uint8(len(signers)) // Create quorum configs per source chain and destination verifiers mapping for chainSelector, verifierAddress := range a.CommitteeVerifierResolverAddresses { @@ -263,11 +284,30 @@ func (a *AggregatorInput) GenerateConfigs(inV []*VerifierInput, generatedConfigF // Add destination verifier mapping committeeConfig.DestinationVerifiers[chainSelStr] = verifierAddress - // Determine threshold for this source + // Determine signers for this source chain + signers := signersPerChain[chainSelector] + // Default threshold is full quorum for this chain (all signers for that chain) + var defaultThreshold uint8 + if len(signers) > 0 { + defaultThreshold = uint8(len(signers)) + } else { + // If no signers explicitly secure this chain, default to 0 + defaultThreshold = 0 + } + + // Determine threshold for this source (override if provided) sourceThreshold := defaultThreshold if a.ThresholdPerSource != nil { - if t, exists := a.ThresholdPerSource[chainSelector]; exists { + // ThresholdPerSource is keyed by chainID + chainID, err := chainsel.GetChainIDFromSelector(chainSelector) + if err != nil { + return nil, fmt.Errorf("failed to get chain ID from chain selector %d: %w", chainSelector, err) + } + if t, exists := a.ThresholdPerSource[chainID]; exists { sourceThreshold = t + } else { + // if threshold per source is provided, but this source is missing, then aggregator will ignore it + continue } } @@ -312,7 +352,6 @@ func (a *AggregatorInput) GenerateConfigs(inV []*VerifierInput, generatedConfigF }) } } - // Marshal main config (without committee - it's in the generated file) mainCfg, err := toml.Marshal(config) if err != nil { diff --git a/build/devenv/services/committeeVerifier.go b/build/devenv/services/committeeVerifier.go index 3a6a25bd0..760bd4ba4 100644 --- a/build/devenv/services/committeeVerifier.go +++ b/build/devenv/services/committeeVerifier.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "path/filepath" + "slices" "strconv" "time" @@ -13,6 +14,8 @@ import ( "github.com/Masterminds/semver/v3" "github.com/docker/docker/api/types/container" "github.com/docker/go-connections/nat" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/postgres" "github.com/testcontainers/testcontainers-go/wait" @@ -32,6 +35,8 @@ import ( "github.com/smartcontractkit/chainlink-testing-framework/framework/components/blockchain" ) +var Slog = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.DebugLevel).With().Fields(map[string]any{"component": "services"}).Logger() + //go:embed committeeVerifier.template.toml var committeeVerifierConfigTemplate string @@ -50,36 +55,6 @@ const ( var DefaultVerifierDBConnectionString = fmt.Sprintf("postgresql://%s:%s@localhost:%d/%s?sslmode=disable", DefaultVerifierName, DefaultVerifierName, DefaultVerifierDBPort, DefaultVerifierName) -// ConvertBlockchainOutputsToInfo converts blockchain.Output to BlockchainInfo. -func ConvertBlockchainOutputsToInfo(outputs []*blockchain.Output) map[string]*protocol.BlockchainInfo { - infos := make(map[string]*protocol.BlockchainInfo) - for _, output := range outputs { - info := &protocol.BlockchainInfo{ - ChainID: output.ChainID, - Type: output.Type, - Family: output.Family, - UniqueChainName: output.ContainerName, - Nodes: make([]*protocol.Node, 0, len(output.Nodes)), - } - - // Convert all nodes - for _, node := range output.Nodes { - if node != nil { - convertedNode := &protocol.Node{ - ExternalHTTPUrl: node.ExternalHTTPUrl, - InternalHTTPUrl: node.InternalHTTPUrl, - ExternalWSUrl: node.ExternalWSUrl, - InternalWSUrl: node.InternalWSUrl, - } - info.Nodes = append(info.Nodes, convertedNode) - } - } - - infos[output.ChainID] = info - } - return infos -} - type VerifierDBInput struct { Image string `toml:"image"` Name string `toml:"name"` @@ -113,6 +88,9 @@ type VerifierInput struct { // Maps to signer_address in the verifier config toml. SigningKeyPublic string `toml:"signing_key_public"` + // The optional list of chain selectors this verifier enables + EnabledChains []string `toml:"enabled_chains"` + // Contract addresses used to generate configs // Maps to on_ramp_addresses in the verifier config toml. OnRampAddresses map[string]string `toml:"on_ramp_addresses"` @@ -450,6 +428,10 @@ func ResolveContractsForVerifier(ds datastore.DataStore, blockchains []*blockcha if err != nil { return VerifierInput{}, err } + if ver.EnabledChains != nil && !slices.Contains(ver.EnabledChains, chain.ChainID) { + Slog.Info().Str("service", "CommitteeVerifier").Msgf("Verifier %s will NOT secure chain %s", ver.ContainerName, chain.ChainID) + continue + } selectorStr := strconv.FormatUint(networkInfo.ChainSelector, 10) onRampAddressRef, err := ds.Addresses().Get(datastore.NewAddressRefKey( diff --git a/build/devenv/tests/e2e/smoke_test.go b/build/devenv/tests/e2e/smoke_test.go index 69cb004bc..dc11ac126 100644 --- a/build/devenv/tests/e2e/smoke_test.go +++ b/build/devenv/tests/e2e/smoke_test.go @@ -115,7 +115,7 @@ func TestE2ESmoke(t *testing.T) { t.Run("extra args v2", func(t *testing.T) { tcs := []v2TestCase{ { - name: "src->dst msg execution eoa receiver", + name: "1337->2337 msg execution eoa receiver", fromSelector: sel0, toSelector: sel1, receiver: mustGetEOAReceiverAddress(t, chainMap[sel1]), @@ -123,7 +123,7 @@ func TestE2ESmoke(t *testing.T) { numExpectedVerifications: 1, }, { - name: "dst->src msg execution eoa receiver", + name: "2337->1337 msg execution eoa receiver", fromSelector: sel1, toSelector: sel0, receiver: mustGetEOAReceiverAddress(t, chainMap[sel0]), @@ -162,6 +162,7 @@ func TestE2ESmoke(t *testing.T) { tcs = append(tcs, mvtcsDestToSrc[0]) tcs = append(tcs, dataSizeTestCases(t, src, dest, in, chainMap)...) tcs = append(tcs, customExecutorTestCase(t, src, dest, in)) + tcs = append(tcs, downSizingV3MessagingTestCases(t, sel0, sel1, sel2, in)...) for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { var receiverStartBalance *big.Int @@ -767,3 +768,122 @@ func multiVerifierTestCases(t *testing.T, src, dest uint64, in *ccv.Cfg, c map[u }, } } + +func downSizingV3MessagingTestCases(t *testing.T, sel0, sel1, sel2 uint64, in *ccv.Cfg) []v3TestCase { + // the list is not exhaustive but not necessary to test all the permutations for correctness, we could add the + // remaining permutation e.g., 2337->3337, 1337->3337, 3337->1337, 2337->1337 but we would effectively be + // testing setup correctness, rather than functional correctness. + return []v3TestCase{ + { + name: "1337->2337 msg tertiary verifier secures 1337 with 2/3 verifiers", + srcSelector: sel0, + dstSelector: sel1, + finality: 1, + receiver: getContractAddress( + t, + in, + sel1, + datastore.ContractType(mock_receiver.ContractType), + mock_receiver.Deploy.Version(), + evm.QuinaryReceiverQualifier, + "quinary mock receiver", + ), + ccvs: []protocol.CCV{ + { + CCVAddress: getContractAddress( + t, + in, + sel0, + datastore.ContractType(committee_verifier.ResolverType), + committee_verifier.Deploy.Version(), + evm.TertiaryCommitteeVerifierQualifier, + "tertiary committee verifier resolver", + ), + Args: []byte{}, + ArgsLen: 0, + }, + }, + // tertiary verifier only but additional verification by the default verifier which + // is the discovery mechanism + numExpectedVerifications: 2, + // default executor and secondary committee verifier and network fee. + numExpectedReceipts: 3, + executor: getContractAddress(t, in, sel0, datastore.ContractType(executor.ProxyType), executor.DeployProxy.Version(), evm.DefaultExecutorQualifier, "executor"), + aggregatorQualifier: evm.TertiaryCommitteeVerifierQualifier, + }, + { + name: "3337->2337 msg tertiary verifier secures 3337 with 1/1 verifiers", + srcSelector: sel2, + dstSelector: sel1, + finality: 1, + receiver: getContractAddress( + t, + in, + sel1, + datastore.ContractType(mock_receiver.ContractType), + mock_receiver.Deploy.Version(), + evm.QuinaryReceiverQualifier, + "quinary mock receiver", + ), + ccvs: []protocol.CCV{ + { + CCVAddress: getContractAddress( + t, + in, + sel2, + datastore.ContractType(committee_verifier.ResolverType), + committee_verifier.Deploy.Version(), + evm.TertiaryCommitteeVerifierQualifier, + "tertiary committee verifier resolver", + ), + Args: []byte{}, + ArgsLen: 0, + }, + }, + // tertiary verifier only but additional verification by the default verifier which + // is the discovery mechanism + numExpectedVerifications: 2, + // default executor and secondary committee verifier and network fee. + numExpectedReceipts: 3, + executor: getContractAddress(t, in, sel2, datastore.ContractType(executor.ProxyType), executor.DeployProxy.Version(), evm.DefaultExecutorQualifier, "executor"), + aggregatorQualifier: evm.TertiaryCommitteeVerifierQualifier, + }, + { + name: "2337->3337 msg tertiary verifier secures 2337 with 1/2 verifiers", + srcSelector: sel2, + dstSelector: sel1, + finality: 1, + receiver: getContractAddress( + t, + in, + sel1, + datastore.ContractType(mock_receiver.ContractType), + mock_receiver.Deploy.Version(), + evm.QuinaryReceiverQualifier, + "quinary mock receiver", + ), + ccvs: []protocol.CCV{ + { + CCVAddress: getContractAddress( + t, + in, + sel2, + datastore.ContractType(committee_verifier.ResolverType), + committee_verifier.Deploy.Version(), + evm.TertiaryCommitteeVerifierQualifier, + "tertiary committee verifier resolver", + ), + Args: []byte{}, + ArgsLen: 0, + }, + }, + // tertiary verifier only but additional verification by the default verifier which + // is the discovery mechanism + numExpectedVerifications: 2, + // default executor and secondary committee verifier and network fee. + numExpectedReceipts: 3, + executor: getContractAddress(t, in, sel2, datastore.ContractType(executor.ProxyType), executor.DeployProxy.Version(), evm.DefaultExecutorQualifier, "executor"), + aggregatorQualifier: evm.TertiaryCommitteeVerifierQualifier, + }, + } +} diff --git a/build/devenv/tests/services/aggregator_test.go b/build/devenv/tests/services/aggregator_test.go index 3db830fc6..0d82fc8da 100644 --- a/build/devenv/tests/services/aggregator_test.go +++ b/build/devenv/tests/services/aggregator_test.go @@ -403,6 +403,8 @@ func (f *aggregatorTestFixture) assertHonestQuorumReached(t *testing.T, ctx cont // setupAggregatorTestFixture creates the test infrastructure with 2-of-3 quorum. func setupAggregatorTestFixture(t *testing.T) *aggregatorTestFixture { committeeName := "test" + // 2337: selector: 12922642891491394802 + sourceChainId := "2337" sourceChainSel := uint64(12922642891491394802) destChainSel := uint64(2) verifierAddress := "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" @@ -429,8 +431,8 @@ func setupAggregatorTestFixture(t *testing.T) *aggregatorTestFixture { CommitteeVerifierResolverAddresses: map[uint64]string{ sourceChainSel: verifierAddress, }, - ThresholdPerSource: map[uint64]uint8{ - sourceChainSel: 2, // 2-of-3 threshold + ThresholdPerSource: map[string]uint8{ + sourceChainId: 2, // 2-of-3 threshold }, AggregationChannelBufferSize: 1, // Minimal buffer for channel exhaustion tests BackgroundWorkerCount: 1, // Single worker = slow drain for deterministic tests