diff --git a/deployment/environment.go b/deployment/environment.go index 8b05e82c..33508232 100644 --- a/deployment/environment.go +++ b/deployment/environment.go @@ -7,6 +7,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/rpc" + "github.com/smartcontractkit/chainlink-deployments-framework/experimental/exceptions" "github.com/smartcontractkit/chainlink-deployments-framework/chain" "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" @@ -60,6 +61,8 @@ type Environment struct { OperationsBundle operations.Bundle // BlockChains is the container of all chains in the environment. BlockChains chain.BlockChains + + Exceptions *exceptions.Exceptions } // EnvironmentOption is a functional option for configuring an Environment diff --git a/engine/cld/domain/envdir.go b/engine/cld/domain/envdir.go index 66d7c7d2..7de6e97b 100644 --- a/engine/cld/domain/envdir.go +++ b/engine/cld/domain/envdir.go @@ -13,6 +13,7 @@ import ( "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/internal/fileutils" "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/internal/jsonutils" "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/nodes" + "github.com/smartcontractkit/chainlink-deployments-framework/experimental/exceptions" ) // EnvDir represents a specific environment directory within a domain. @@ -469,3 +470,13 @@ func (d EnvDir) CreateDurablePipelinesDir() error { func (d EnvDir) SaveViewState(v json.Marshaler) error { return SaveViewState(d.ViewStateFilePath(), v) } + +// ExceptionsFilePath returns the path to the exceptions file for the domain's environment directory. +func (d EnvDir) ExceptionsFilePath() string { + return filepath.Join(d.DirPath(), ExceptionsFileName) +} + +// LoadExceptions loads the exceptions from the exceptions file for the domain's environment directory. +func (d EnvDir) LoadExceptions() (*exceptions.Exceptions, error) { + return exceptions.Load(d.ExceptionsFilePath()) +} diff --git a/engine/cld/domain/paths.go b/engine/cld/domain/paths.go index 9a692062..801cef32 100644 --- a/engine/cld/domain/paths.go +++ b/engine/cld/domain/paths.go @@ -87,4 +87,8 @@ const ( // DurablePipelineInputsDirName is the name of the directory containing the inputs // for the durable pipelines. DurablePipelineInputsDirName = "inputs" + + // ExceptionsFileName is the name of the file containing exceptions. + // At the moment, this is only used for CCIP domain. + ExceptionsFileName = "exceptions.yaml" ) diff --git a/engine/cld/environment/environment.go b/engine/cld/environment/environment.go index a8da689a..b583864f 100644 --- a/engine/cld/environment/environment.go +++ b/engine/cld/environment/environment.go @@ -128,6 +128,11 @@ func Load( } } + exceptions, err := envdir.LoadExceptions() + if err != nil { + return fdeployment.Environment{}, fmt.Errorf("failed to load exceptions: %w", err) + } + getCtx := func() context.Context { return ctx } return fdeployment.Environment{ @@ -141,5 +146,6 @@ func Load( OCRSecrets: sharedSecrets, OperationsBundle: operations.NewBundle(getCtx, lggr, loadcfg.reporter, operations.WithOperationRegistry(loadcfg.operationRegistry)), BlockChains: blockChains, + Exceptions: exceptions, }, nil } diff --git a/experimental/exceptions/exceptions.go b/experimental/exceptions/exceptions.go new file mode 100644 index 00000000..9c5da4c8 --- /dev/null +++ b/experimental/exceptions/exceptions.go @@ -0,0 +1,62 @@ +package exceptions + +import ( + "os" + + "github.com/ethereum/go-ethereum/common" + "gopkg.in/yaml.v3" +) + +// ExceptionType defines the type of exception. +type ExceptionType string + +const ( + ExceptionTypeRateLimit ExceptionType = "RateLimit" + ExceptionTypeDeprecated ExceptionType = "Deprecated" +) + +// Exception represents a single exception entry. +type Exception struct { + Resource string `yaml:"resource"` + Address common.Address `yaml:"address"` + Reason string `yaml:"reason"` + Type ExceptionType `yaml:"type"` +} + +// Exceptions holds a mapping of network names to their respective exceptions. +type Exceptions struct { + Exceptions map[string][]Exception `yaml:"exceptions"` +} + +// Load loads exceptions from a YAML file at the specified path. +func Load(filePath string) (*Exceptions, error) { + d, err := os.ReadFile(filePath) + if err != nil { + return nil, err + } + + var e Exceptions + if err := yaml.Unmarshal(d, &e); err != nil { + return nil, err + } + + return &e, nil +} + +// IsExpected checks if the given address on the specified network is an expected exception of the given type. +func (e *Exceptions) IsExpected(network string, address common.Address, exceptionType ExceptionType) (bool, string) { + if _, ok := e.Exceptions[network]; ok { + return e.isAddressExpected(network, address, exceptionType) + } + return false, "" +} + +// isAddressExpected checks if the address is in the exceptions for the given network and type. +func (e *Exceptions) isAddressExpected(network string, address common.Address, exceptionType ExceptionType) (bool, string) { + for _, exception := range e.Exceptions[network] { + if exception.Address == address && exception.Type == exceptionType { + return true, exception.Reason + } + } + return false, "" +}