@@ -2,9 +2,11 @@ package main
22
33import (
44 "context"
5+ "encoding/json"
56 "fmt"
67 "os"
78 "os/exec"
9+ "path/filepath"
810 "strconv"
911 "strings"
1012 "syscall"
@@ -20,6 +22,9 @@ import (
2022
2123 "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/committee_verifier"
2224 "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/mock_receiver"
25+ offrampoperations "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/offramp"
26+ onrampoperations "github.com/smartcontractkit/chainlink-ccip/ccv/chains/evm/deployment/v1_7_0/operations/onramp"
27+ "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_6_0/operations/rmn_remote"
2328 "github.com/smartcontractkit/chainlink-ccv/devenv/cciptestinterfaces"
2429 "github.com/smartcontractkit/chainlink-ccv/devenv/services"
2530 "github.com/smartcontractkit/chainlink-ccv/protocol"
@@ -442,6 +447,223 @@ var printAddressesCmd = &cobra.Command{
442447 },
443448}
444449
450+ var generateConfigsCmd = & cobra.Command {
451+ Use : "generate-configs" ,
452+ Short : "Generate the verifier and executor jobspecs (CL deployment only), and the aggregator and indexer TOML configuration files for the environment" ,
453+ RunE : func (cmd * cobra.Command , args []string ) error {
454+ // TODO: maybe move the actual generation logic into a function
455+ // so that it can potentially be re-used (maybe from CLD?)
456+ addressRefsPath , err := cmd .Flags ().GetString ("address-refs-json" )
457+ if err != nil {
458+ return err
459+ }
460+ verifierPubKeys , err := cmd .Flags ().GetStringSlice ("verifier-pubkeys" )
461+ if err != nil {
462+ return err
463+ }
464+ aggregatorAddr , err := cmd .Flags ().GetString ("aggregator-addr" )
465+ if err != nil {
466+ return err
467+ }
468+ aggregatorPort , err := cmd .Flags ().GetInt ("aggregator-port" )
469+ if err != nil {
470+ return err
471+ }
472+ numExecutors , err := cmd .Flags ().GetInt ("num-executors" )
473+ if err != nil {
474+ return err
475+ }
476+ if numExecutors == - 1 {
477+ numExecutors = len (verifierPubKeys )
478+ }
479+ if numExecutors > len (verifierPubKeys ) {
480+ return fmt .Errorf ("number of executors cannot be greater than number of verifiers" )
481+ }
482+ indexerAddress , err := cmd .Flags ().GetString ("indexer-addr" )
483+ if err != nil {
484+ return err
485+ }
486+ monitoringOtelExporterHTTPEndpoint , err := cmd .Flags ().GetString ("monitoring-otel-exporter-http-endpoint" )
487+ if err != nil {
488+ return err
489+ }
490+
491+ ocrThreshold := func (n int ) uint8 {
492+ f := (n - 1 ) / 3 // n = 3f + 1 => f = (n - 1) / 3
493+ return uint8 (f + 1 ) // OCR threshold is f + 1
494+ }
495+
496+ ccv .Plog .Info ().
497+ Str ("address-refs-json" , addressRefsPath ).
498+ Strs ("verifier-pubkeys" , verifierPubKeys ).
499+ Str ("aggregator-addr" , aggregatorAddr ).
500+ Int ("aggregator-port" , aggregatorPort ).
501+ Int ("num-executors" , numExecutors ).
502+ Msg ("Generating configs" )
503+
504+ // Load the address refs from the JSON file
505+ f , err := os .Open (addressRefsPath )
506+ if err != nil {
507+ return fmt .Errorf ("failed to open address refs JSON file: %w" , err )
508+ }
509+ defer f .Close ()
510+
511+ decoder := json .NewDecoder (f )
512+ var addressRefs []datastore.AddressRef
513+ if err := decoder .Decode (& addressRefs ); err != nil {
514+ return fmt .Errorf ("failed to decode address refs JSON: %w" , err )
515+ }
516+
517+ const (
518+ verifierIDPrefix = "default-verifier-"
519+ executorIDPrefix = "default-executor-"
520+ committeeName = "default"
521+ )
522+ var (
523+ onRampAddresses = make (map [string ]string )
524+ // TODO: both maps below store the same data, just the key type is different
525+ committeeVerifierAddresses = make (map [string ]string )
526+ committeeVerifierResolverProxyAddresses = make (map [uint64 ]string )
527+ defaultExecutorOnRampAddresses = make (map [string ]string )
528+ defaultExecutorOnRampAddressesUint64 = make (map [uint64 ]string )
529+ rmnRemoteAddresses = make (map [string ]string )
530+ rmnRemoteAddressesUint64 = make (map [uint64 ]string )
531+ offRampAddresses = make (map [uint64 ]string )
532+ thresholdPerSource = make (map [uint64 ]uint8 )
533+ blockchainInfos = make (map [string ]* protocol.BlockchainInfo )
534+ )
535+ for _ , ref := range addressRefs {
536+ chainSelectorStr := strconv .FormatUint (ref .ChainSelector , 10 )
537+ switch ref .Type {
538+ case datastore .ContractType (onrampoperations .ContractType ):
539+ onRampAddresses [chainSelectorStr ] = ref .Address
540+ case datastore .ContractType (committee_verifier .ResolverProxyType ):
541+ committeeVerifierAddresses [chainSelectorStr ] = ref .Address
542+ committeeVerifierResolverProxyAddresses [ref .ChainSelector ] = ref .Address
543+ case datastore .ContractType (executor_operations .ContractType ):
544+ defaultExecutorOnRampAddresses [chainSelectorStr ] = ref .Address
545+ defaultExecutorOnRampAddressesUint64 [ref .ChainSelector ] = ref .Address
546+ case datastore .ContractType (rmn_remote .ContractType ):
547+ rmnRemoteAddresses [chainSelectorStr ] = ref .Address
548+ rmnRemoteAddressesUint64 [ref .ChainSelector ] = ref .Address
549+ case datastore .ContractType (offrampoperations .ContractType ):
550+ offRampAddresses [ref .ChainSelector ] = ref .Address
551+ }
552+ thresholdPerSource [ref .ChainSelector ] = ocrThreshold (len (verifierPubKeys ))
553+
554+ // TODO: these values don't really matter for deployments that use the chainlink node.
555+ // Blockchain infos should be moved to a separate config for standalone mode verifiers.
556+ blockchainInfos [chainSelectorStr ] = & protocol.BlockchainInfo {
557+ ChainID : chainSelectorStr ,
558+ Type : "evm" ,
559+ Family : "evm" ,
560+ UniqueChainName : fmt .Sprintf ("blockchain-%s" , chainSelectorStr ),
561+ Nodes : []* protocol.Node {
562+ {
563+ ExternalHTTPUrl : fmt .Sprintf ("some-random-http-url-%s" , chainSelectorStr ),
564+ InternalHTTPUrl : fmt .Sprintf ("some-random-internal-http-url-%s" , chainSelectorStr ),
565+ ExternalWSUrl : fmt .Sprintf ("some-random-ws-url-%s" , chainSelectorStr ),
566+ InternalWSUrl : fmt .Sprintf ("some-random-internal-ws-url-%s" , chainSelectorStr ),
567+ },
568+ },
569+ }
570+ }
571+
572+ // create temporary directory to store the generated configs
573+ tempDir , err := os .MkdirTemp ("" , "ccv-configs" )
574+ if err != nil {
575+ return fmt .Errorf ("failed to create temporary directory: %w" , err )
576+ }
577+ ccv .Plog .Info ().Str ("temp-dir" , tempDir ).Msg ("Created temporary directory for configs" )
578+
579+ // create the VerifierInput for each verifier
580+ verifierInputs := make ([]* services.VerifierInput , 0 , len (verifierPubKeys ))
581+ for i , pubKey := range verifierPubKeys {
582+ verifierInputs = append (verifierInputs , & services.VerifierInput {
583+ ContainerName : fmt .Sprintf ("%s%d" , verifierIDPrefix , i ),
584+ AggregatorAddress : fmt .Sprintf ("%s:%d" , aggregatorAddr , aggregatorPort ),
585+ SigningKeyPublic : pubKey ,
586+ CommitteeVerifierAddresses : committeeVerifierAddresses ,
587+ OnRampAddresses : onRampAddresses ,
588+ DefaultExecutorOnRampAddresses : defaultExecutorOnRampAddresses ,
589+ RMNRemoteAddresses : rmnRemoteAddresses ,
590+ CommitteeName : committeeName ,
591+ MonitoringOtelExporterHTTPEndpoint : monitoringOtelExporterHTTPEndpoint ,
592+ BlockchainInfos : blockchainInfos ,
593+ })
594+ }
595+ // generate and print the job spec to stdout for now
596+ for _ , verifierInput := range verifierInputs {
597+ verifierJobSpec , err := verifierInput .GenerateJobSpec ()
598+ if err != nil {
599+ return fmt .Errorf ("failed to generate verifier job spec: %w" , err )
600+ }
601+ ccv .Plog .Info ().Msg ("Generated verifier job spec, writing to temporary directory as a separate file" )
602+ // write to a file in the temporary directory generated above
603+ filePath := filepath .Join (tempDir , fmt .Sprintf ("verifier-%s-job-spec.toml" , verifierInput .ContainerName ))
604+ if err := os .WriteFile (filePath , []byte (verifierJobSpec ), 0o644 ); err != nil {
605+ return fmt .Errorf ("failed to write verifier job spec to file: %w" , err )
606+ }
607+ ccv .Plog .Info ().Str ("file-path" , filePath ).Msg ("Wrote verifier job spec to file" )
608+ }
609+
610+ // create the ExecutorInput for each executor
611+ executorInputs := make ([]services.ExecutorInput , 0 , numExecutors )
612+ // create executor pool first
613+ executorPool := make ([]string , 0 , numExecutors )
614+ for i := 0 ; i < numExecutors ; i ++ {
615+ executorPool = append (executorPool , fmt .Sprintf ("%s%d" , executorIDPrefix , i ))
616+ }
617+ for i := 0 ; i < numExecutors ; i ++ {
618+ executorInputs = append (executorInputs , services.ExecutorInput {
619+ ExecutorID : fmt .Sprintf ("%s%d" , executorIDPrefix , i ),
620+ ExecutorPool : executorPool ,
621+ OfframpAddresses : offRampAddresses ,
622+ IndexerAddress : indexerAddress ,
623+ ExecutorAddresses : defaultExecutorOnRampAddressesUint64 ,
624+ RmnAddresses : rmnRemoteAddressesUint64 ,
625+ BlockchainInfos : blockchainInfos ,
626+ })
627+ }
628+ // generate and print the config to stdout for now
629+ for _ , executorInput := range executorInputs {
630+ executorJobSpec , err := executorInput .GenerateJobSpec ()
631+ if err != nil {
632+ return fmt .Errorf ("failed to generate executor job spec: %w" , err )
633+ }
634+ ccv .Plog .Info ().Msg ("Generated executor job spec, writing to temporary directory as a separate file" )
635+ // write to a file in the temporary directory generated above
636+ filePath := filepath .Join (tempDir , fmt .Sprintf ("executor-%s-job-spec.toml" , executorInput .ExecutorID ))
637+ if err := os .WriteFile (filePath , []byte (executorJobSpec ), 0o644 ); err != nil {
638+ return fmt .Errorf ("failed to write executor job spec to file: %w" , err )
639+ }
640+ ccv .Plog .Info ().Str ("file-path" , filePath ).Msg ("Wrote executor job spec to file" )
641+ }
642+
643+ // Create the AggregatorInput
644+ aggregatorInput := services.AggregatorInput {
645+ CommitteeName : committeeName ,
646+ CommitteeVerifierResolverProxyAddresses : committeeVerifierResolverProxyAddresses ,
647+ ThresholdPerSource : thresholdPerSource ,
648+ MonitoringOtelExporterHTTPEndpoint : monitoringOtelExporterHTTPEndpoint ,
649+ }
650+ // generate and print the config to stdout for now
651+ aggregatorConfig , err := aggregatorInput .GenerateConfig (verifierInputs )
652+ if err != nil {
653+ return fmt .Errorf ("failed to generate aggregator config: %w" , err )
654+ }
655+ ccv .Plog .Info ().Msg ("Generated aggregator config:" )
656+ // write to a file in the temporary directory generated above
657+ filePath := filepath .Join (tempDir , "aggregator-config.toml" )
658+ if err := os .WriteFile (filePath , aggregatorConfig , 0o644 ); err != nil {
659+ return fmt .Errorf ("failed to write aggregator config to file: %w" , err )
660+ }
661+ ccv .Plog .Info ().Str ("file-path" , filePath ).Msg ("Wrote aggregator config to file" )
662+
663+ return nil
664+ },
665+ }
666+
445667var monitorContractsCmd = & cobra.Command {
446668 Use : "upload-on-chain-metrics <source> <dest>" ,
447669 Short : "Reads on-chain EVM contract events and temporary exposes them as Prometheus metrics endpoint to be scraped" ,
@@ -542,6 +764,11 @@ var sendCmd = &cobra.Command{
542764 ctx := context .Background ()
543765 ctx = ccv .Plog .WithContext (ctx )
544766
767+ receiverQualifier , err := cmd .Flags ().GetString ("receiver-qualifier" )
768+ if err != nil {
769+ return fmt .Errorf ("failed to parse 'receiver-qualifier' flag: %w" , err )
770+ }
771+
545772 // Read the env flag, default to "out"
546773 envName , err := cmd .Flags ().GetString ("env" )
547774 if err != nil {
@@ -591,7 +818,7 @@ var sendCmd = &cobra.Command{
591818 dest ,
592819 datastore .ContractType (mock_receiver .ContractType ),
593820 semver .MustParse (mock_receiver .Deploy .Version ()),
594- evm . DefaultReceiverQualifier ))
821+ receiverQualifier ))
595822 if err != nil {
596823 return fmt .Errorf ("failed to get mock receiver address: %w" , err )
597824 }
@@ -691,6 +918,7 @@ func init() {
691918 rootCmd .AddCommand (printAddressesCmd )
692919 rootCmd .AddCommand (sendCmd )
693920 sendCmd .Flags ().String ("env" , "out" , "Select environment file to use (e.g., 'staging' for env-staging.toml, defaults to env-out.toml)" )
921+ sendCmd .Flags ().String ("receiver-qualifier" , evm .DefaultReceiverQualifier , "Receiver qualifier to use for the mock receiver contract" )
694922
695923 // on-chain monitoring
696924 rootCmd .AddCommand (monitorContractsCmd )
@@ -699,6 +927,23 @@ func init() {
699927 // contract management
700928 rootCmd .AddCommand (deployCommitVerifierCmd )
701929 rootCmd .AddCommand (deployReceiverCmd )
930+
931+ // config generation
932+ rootCmd .AddCommand (generateConfigsCmd )
933+ generateConfigsCmd .Flags ().String ("address-refs-json" , "" , "Path to the CLD address_refs.json file" )
934+ generateConfigsCmd .Flags ().StringSlice ("verifier-pubkeys" , []string {}, "List of verifier public keys (comma separated), implies number of verifiers to generate configs for" )
935+ generateConfigsCmd .Flags ().String ("aggregator-addr" , "" , "Aggregator gRPC address" )
936+ generateConfigsCmd .Flags ().Int ("aggregator-port" , 0 , "Aggregator gRPC port" )
937+ generateConfigsCmd .Flags ().String ("indexer-addr" , "" , "Indexer HTTP/s URL address" )
938+ generateConfigsCmd .Flags ().String ("monitoring-otel-exporter-http-endpoint" , "" , "Monitoring OpenTelemetry HTTP endpoint, e.g. otel-collector:4318" )
939+ generateConfigsCmd .Flags ().Int ("num-executors" , - 1 , "Number of executors to generate configs for, defaults to number of verifiers if not provided" )
940+
941+ _ = generateConfigsCmd .MarkFlagRequired ("address-refs-json" )
942+ _ = generateConfigsCmd .MarkFlagRequired ("verifier-pubkeys" )
943+ _ = generateConfigsCmd .MarkFlagRequired ("aggregator-addr" )
944+ _ = generateConfigsCmd .MarkFlagRequired ("aggregator-port" )
945+ _ = generateConfigsCmd .MarkFlagRequired ("indexer-addr" )
946+ _ = generateConfigsCmd .MarkFlagRequired ("monitoring-otel-exporter-http-endpoint" )
702947}
703948
704949func checkDockerIsRunning () {
0 commit comments