From 45f6fc325f15aa3a6dff06cd4f57983bc98dcef3 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Wed, 29 Jan 2025 19:14:04 +0400 Subject: [PATCH 01/24] initial version --- pkg/reader/ccip.go | 255 +++++--------------- pkg/reader/config_cache.go | 368 +++++++++++++++++++++++++++++ pkg/types/ccipocr3/config_types.go | 22 ++ 3 files changed, 455 insertions(+), 190 deletions(-) create mode 100644 pkg/reader/config_cache.go create mode 100644 pkg/types/ccipocr3/config_types.go diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index a4970be97..cbfffb4b1 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "math/big" - "sort" "strconv" "sync" "time" @@ -43,6 +42,7 @@ type ccipChainReader struct { destChain cciptypes.ChainSelector offrampAddress string extraDataCodec cciptypes.ExtraDataCodec + caches map[cciptypes.ChainSelector]*configCache } func newCCIPChainReaderInternal( @@ -68,6 +68,11 @@ func newCCIPChainReaderInternal( extraDataCodec: extraDataCodec, } + // Initialize caches for each chain selector + for chainSelector := range contractReaders { + reader.caches[chainSelector] = newConfigCache(reader) + } + contracts := ContractAddresses{ consts.ContractNameOffRamp: { destChain: offrampAddress, @@ -662,59 +667,30 @@ func (r *ccipChainReader) GetChainFeePriceUpdate(ctx context.Context, selectors return feeUpdates } -func (r *ccipChainReader) GetRMNRemoteConfig( - ctx context.Context, - destChainSelector cciptypes.ChainSelector, -) (rmntypes.RemoteConfig, error) { - lggr := logutil.WithContextValues(ctx, r.lggr) +func (r *ccipChainReader) GetRMNRemoteConfig(ctx context.Context, destChainSelector cciptypes.ChainSelector) (rmntypes.RemoteConfig, error) { if err := validateExtendedReaderExistence(r.contractReaders, destChainSelector); err != nil { return rmntypes.RemoteConfig{}, err } - // RMNRemote address stored in the offramp static config is actually the proxy contract address. - // Here we will get the RMNRemote address from the proxy contract by calling the RMNProxy contract. - proxyContractAddress, err := r.GetContractAddress(consts.ContractNameRMNRemote, destChainSelector) - if err != nil { - return rmntypes.RemoteConfig{}, fmt.Errorf("get RMNRemote proxy contract address: %w", err) + cache, ok := r.caches[destChainSelector] + if !ok { + return rmntypes.RemoteConfig{}, fmt.Errorf("cache not found for chain %d", destChainSelector) } - rmnRemoteAddress, err := r.getRMNRemoteAddress(ctx, lggr, destChainSelector, proxyContractAddress) + rmnRemoteAddress, err := cache.GetRMNRemoteAddress(ctx) if err != nil { - return rmntypes.RemoteConfig{}, fmt.Errorf("get RMNRemote address: %w", err) + return rmntypes.RemoteConfig{}, fmt.Errorf("get RMN remote address: %w", err) } - lggr.Debugw("got RMNRemote address", "address", rmnRemoteAddress) - // TODO: make the calls in parallel using errgroup - var vc versionedConfig - err = r.contractReaders[destChainSelector].ExtendedGetLatestValue( - ctx, - consts.ContractNameRMNRemote, - consts.MethodNameGetVersionedConfig, - primitives.Unconfirmed, - map[string]any{}, - &vc, - ) + vc, err := cache.GetRMNVersionedConfig(ctx) if err != nil { - return rmntypes.RemoteConfig{}, fmt.Errorf("get RMNRemote config: %w", err) - } - - type ret struct { - DigestHeader cciptypes.Bytes32 + return rmntypes.RemoteConfig{}, fmt.Errorf("get RMN versioned config: %w", err) } - var header ret - err = r.contractReaders[destChainSelector].ExtendedGetLatestValue( - ctx, - consts.ContractNameRMNRemote, - consts.MethodNameGetReportDigestHeader, - primitives.Unconfirmed, - map[string]any{}, - &header, - ) + header, err := cache.GetRMNDigestHeader(ctx) if err != nil { - return rmntypes.RemoteConfig{}, fmt.Errorf("get RMNRemote report digest header: %w", err) + return rmntypes.RemoteConfig{}, fmt.Errorf("get RMN digest header: %w", err) } - lggr.Infow("got RMNRemote report digest header", "digest", header.DigestHeader) signers := make([]rmntypes.RemoteSignerInfo, 0, len(vc.Config.Signers)) for _, signer := range vc.Config.Signers { @@ -725,7 +701,7 @@ func (r *ccipChainReader) GetRMNRemoteConfig( } return rmntypes.RemoteConfig{ - ContractAddress: rmnRemoteAddress, + ContractAddress: cciptypes.UnknownAddress(rmnRemoteAddress), ConfigDigest: vc.Config.RMNHomeContractConfigDigest, Signers: signers, FSign: vc.Config.F, @@ -813,21 +789,24 @@ func (r *ccipChainReader) discoverOffRampContracts( return nil, fmt.Errorf("validate extended reader existence: %w", err) } - // build up resp as we go. + cache, ok := r.caches[chain] + if !ok { + return nil, fmt.Errorf("cache not found for chain selector %d", chain) + } + var resp ContractAddresses // OnRamps are in the offRamp SourceChainConfig. { - sourceConfigs, err := r.getAllOffRampSourceChainsConfig(ctx, lggr, chain) + selectorsAndConfigs, err := cache.GetOffRampAllChains(ctx) if err != nil { return nil, fmt.Errorf("unable to get SourceChainsConfig: %w", err) } // Iterate results in sourceChain selector order so that the router config is deterministic. - keys := maps.Keys(sourceConfigs) - sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) - for _, sourceChain := range keys { - cfg := sourceConfigs[sourceChain] + for i := range selectorsAndConfigs.Selectors { + sourceChain := cciptypes.ChainSelector(selectorsAndConfigs.Selectors[i]) + cfg := selectorsAndConfigs.SourceChainConfigs[i] resp = resp.Append(consts.ContractNameOnRamp, sourceChain, cfg.OnRamp) // The local router is located in each source sourceChain config. Add it once. if len(resp[consts.ContractNameRouter][chain]) == 0 { @@ -839,14 +818,7 @@ func (r *ccipChainReader) discoverOffRampContracts( // NonceManager and RMNRemote are in the offramp static config. { - var staticConfig offRampStaticChainConfig - err := r.getDestinationData( - ctx, - chain, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetStaticConfig, - &staticConfig, - ) + staticConfig, err := cache.GetOffRampStaticConfig(ctx) if err != nil { return nil, fmt.Errorf("unable to lookup nonce manager and rmn proxy remote (offramp static config): %w", err) } @@ -857,14 +829,7 @@ func (r *ccipChainReader) discoverOffRampContracts( // FeeQuoter from the offRamp dynamic config. { - var dynamicConfig offRampDynamicChainConfig - err := r.getDestinationData( - ctx, - chain, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetDynamicConfig, - &dynamicConfig, - ) + dynamicConfig, err := cache.GetOffRampDynamicConfig(ctx) if err != nil { return nil, fmt.Errorf("unable to lookup fee quoter (offramp dynamic config): %w", err) } @@ -1005,20 +970,13 @@ type feeQuoterStaticConfig struct { // getDestFeeQuoterStaticConfig returns the destination chain's Fee Quoter's StaticConfig func (r *ccipChainReader) getDestFeeQuoterStaticConfig(ctx context.Context) (feeQuoterStaticConfig, error) { - var staticConfig feeQuoterStaticConfig - err := r.getDestinationData( - ctx, - r.destChain, - consts.ContractNameFeeQuoter, - consts.MethodNameFeeQuoterGetStaticConfig, - &staticConfig, - ) + cache, ok := r.caches[r.destChain] - if err != nil { - return feeQuoterStaticConfig{}, fmt.Errorf("unable to lookup fee quoter (offramp static config): %w", err) + if !ok { + return feeQuoterStaticConfig{}, fmt.Errorf("cache not found for chain %d", r.destChain) } - return staticConfig, nil + return cache.GetFeeQuoterConfig(ctx) } // getFeeQuoterTokenPriceUSD gets the token price in USD of the given token address from the FeeQuoter contract on the @@ -1157,24 +1115,14 @@ func (r *ccipChainReader) getAllOffRampSourceChainsConfig( lggr logger.Logger, chain cciptypes.ChainSelector, ) (map[cciptypes.ChainSelector]sourceChainConfig, error) { - if err := validateExtendedReaderExistence(r.contractReaders, chain); err != nil { - return nil, fmt.Errorf("validate extended reader existence: %w", err) + cache, ok := r.caches[chain] + if !ok { + return nil, fmt.Errorf("cache not found for chain selector %d", chain) } - configs := make(map[cciptypes.ChainSelector]sourceChainConfig) - - var resp selectorsAndConfigs - err := r.contractReaders[chain].ExtendedGetLatestValue( - ctx, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetAllSourceChainConfigs, - primitives.Unconfirmed, - map[string]any{}, - &resp, - ) + resp, err := cache.GetOffRampAllChains(ctx) if err != nil { - return nil, fmt.Errorf("failed to get source chain configs for source chain %d: %w", - chain, err) + return nil, fmt.Errorf("failed to get source chain configs from cache for chain %d: %w", chain, err) } if len(resp.SourceChainConfigs) != len(resp.Selectors) { @@ -1183,7 +1131,9 @@ func (r *ccipChainReader) getAllOffRampSourceChainsConfig( lggr.Debugw("got source chain configs", "configs", resp) - // Populate the map. + enabledConfigs := make(map[cciptypes.ChainSelector]sourceChainConfig) + + // Populate the map and filter out disabled chains for i := range resp.Selectors { chainSel := cciptypes.ChainSelector(resp.Selectors[i]) cfg := resp.SourceChainConfigs[i] @@ -1198,10 +1148,10 @@ func (r *ccipChainReader) getAllOffRampSourceChainsConfig( continue } - configs[chainSel] = cfg + enabledConfigs[chainSel] = cfg } - return configs, nil + return enabledConfigs, nil } // offRampStaticChainConfig is used to parse the response from the offRamp contract's getStaticConfig method. @@ -1272,53 +1222,35 @@ func (r *ccipChainReader) getOnRampDynamicConfigs( ) map[cciptypes.ChainSelector]getOnRampDynamicConfigResponse { result := make(map[cciptypes.ChainSelector]getOnRampDynamicConfigResponse) - mu := new(sync.Mutex) - wg := new(sync.WaitGroup) for _, chainSel := range srcChains { // no onramp for the destination chain if chainSel == r.destChain { continue } - if r.contractReaders[chainSel] == nil { - r.lggr.Errorw("contract reader not found", "chain", chainSel) + + cache, ok := r.caches[chainSel] + if !ok { + lggr.Errorw("cache not found for chain selector", "chain", chainSel) continue } - wg.Add(1) - go func(chainSel cciptypes.ChainSelector) { - defer wg.Done() - // read onramp dynamic config - resp := getOnRampDynamicConfigResponse{} - err := r.contractReaders[chainSel].ExtendedGetLatestValue( - ctx, - consts.ContractNameOnRamp, - consts.MethodNameOnRampGetDynamicConfig, - primitives.Unconfirmed, - map[string]any{}, - &resp, - ) - lggr.Debugw("got onramp dynamic config", - "chain", chainSel, - "resp", resp) - if err != nil { - if errors.Is(err, contractreader.ErrNoBindings) { - // ErrNoBindings is an allowable error during initialization - lggr.Infow( - "unable to lookup source fee quoters (onRamp dynamic config), "+ - "this is expected during initialization", "err", err) - } else { - lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", - "chain", chainSel, "err", err) - } - return + resp, err := cache.GetOnRampDynamicConfig(ctx) + if err != nil { + if errors.Is(err, contractreader.ErrNoBindings) { + // ErrNoBindings is an allowable error during initialization + lggr.Infow( + "unable to lookup source fee quoters (onRamp dynamic config), "+ + "this is expected during initialization", "err", err) + } else { + lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", + "chain", chainSel, "err", err) } - mu.Lock() - result[chainSel] = resp - mu.Unlock() - }(chainSel) - } + continue + } - wg.Wait() + lggr.Debugw("got onramp dynamic config", "chain", chainSel, "resp", resp) + result[chainSel] = resp + } return result } @@ -1408,36 +1340,6 @@ type versionedConfig struct { Config config `json:"config"` } -// getARM gets the RMN remote address from the RMN proxy address. -// See: https://github.com/smartcontractkit/chainlink/blob/3c7817c566c5d0aa14519c679fa85b227ac97cc5/contracts/src/v0.8/ccip/rmn/ARMProxy.sol#L40-L44 -// -//nolint:lll -func (r *ccipChainReader) getRMNRemoteAddress( - ctx context.Context, - lggr logger.Logger, - chain cciptypes.ChainSelector, - rmnRemoteProxyAddress []byte) ([]byte, error) { - _, err := bindExtendedReaderContract(ctx, lggr, r.contractReaders, chain, consts.ContractNameRMNProxy, rmnRemoteProxyAddress) - if err != nil { - return nil, fmt.Errorf("bind RMN proxy contract: %w", err) - } - - // get the RMN remote address from the proxy - var rmnRemoteAddress []byte - err = r.getDestinationData( - ctx, - chain, - consts.ContractNameRMNProxy, - consts.MethodNameGetARM, - &rmnRemoteAddress, - ) - if err != nil { - return nil, fmt.Errorf("unable to lookup RMN remote address (RMN proxy): %w", err) - } - - return rmnRemoteAddress, nil -} - // Get the DestChainConfig from the FeeQuoter contract on the given chain. func (r *ccipChainReader) getFeeQuoterDestChainConfig( ctx context.Context, @@ -1533,39 +1435,12 @@ func (r *ccipChainReader) GetOffRampConfigDigest(ctx context.Context, pluginType return [32]byte{}, fmt.Errorf("validate dest=%d extended reader existence: %w", r.destChain, err) } - type ConfigInfo struct { - ConfigDigest [32]byte - F uint8 - N uint8 - IsSignatureVerificationEnabled bool - } - - type OCRConfig struct { - ConfigInfo ConfigInfo - Signers [][]byte - Transmitters [][]byte - } - - type OCRConfigResponse struct { - OCRConfig OCRConfig - } - - var resp OCRConfigResponse - err := r.contractReaders[r.destChain].ExtendedGetLatestValue( - ctx, - consts.ContractNameOffRamp, - consts.MethodNameOffRampLatestConfigDetails, - primitives.Unconfirmed, - map[string]any{ - "ocrPluginType": pluginType, - }, - &resp, - ) - if err != nil { - return [32]byte{}, fmt.Errorf("get latest config digest: %w", err) + cache, ok := r.caches[r.destChain] + if !ok { + return [32]byte{}, fmt.Errorf("cache not found for chain %d", r.destChain) } - return resp.OCRConfig.ConfigInfo.ConfigDigest, nil + return cache.GetOffRampConfigDigest(ctx, pluginType) } // Interface compliance check diff --git a/pkg/reader/config_cache.go b/pkg/reader/config_cache.go new file mode 100644 index 000000000..1f1592846 --- /dev/null +++ b/pkg/reader/config_cache.go @@ -0,0 +1,368 @@ +package reader + +import ( + "context" + "fmt" + "sync" + "time" + + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + "github.com/smartcontractkit/chainlink-common/pkg/types" +) + +const ( + configCacheRefreshInterval = 30 * time.Second +) + +// configCache handles caching of contract configurations with automatic refresh +type configCache struct { + reader *ccipChainReader + cacheMu sync.RWMutex + lastUpdateAt time.Time + + // Internal state + nativeTokenAddress cciptypes.Bytes + commitLatestOCRConfig cciptypes.OCRConfigResponse + execLatestOCRConfig cciptypes.OCRConfigResponse + offrampStaticConfig offRampStaticChainConfig + offrampDynamicConfig offRampDynamicChainConfig + offrampAllChains selectorsAndConfigs + onrampDynamicConfig getOnRampDynamicConfigResponse + rmnDigestHeader cciptypes.RMNDigestHeader + rmnVersionedConfig versionedConfig + rmnRemoteAddress cciptypes.Bytes + feeQuoterConfig feeQuoterStaticConfig +} + +// newConfigCache creates a new instance of the configuration cache +func newConfigCache(reader *ccipChainReader) *configCache { + return &configCache{ + reader: reader, + } +} + +// refreshIfNeeded refreshes the cache if the refresh interval has elapsed +func (c *configCache) refreshIfNeeded(ctx context.Context) error { + c.cacheMu.Lock() + defer c.cacheMu.Unlock() + + if time.Since(c.lastUpdateAt) < configCacheRefreshInterval { + return nil + } + + if err := c.refresh(ctx); err != nil { + return fmt.Errorf("refresh cache: %w", err) + } + + c.lastUpdateAt = time.Now() + return nil +} + +// refresh fetches all configurations and updates the cache +func (c *configCache) refresh(ctx context.Context) error { + requests := c.prepareBatchRequests() + + batchResult, err := c.reader.contractReaders[c.reader.destChain].ExtendedBatchGetLatestValues(ctx, requests) + if err != nil { + return fmt.Errorf("batch get configs: %w", err) + } + + if err := c.updateFromResults(batchResult); err != nil { + return fmt.Errorf("update cache from results: %w", err) + } + + return nil +} + +// prepareBatchRequests creates the batch request for all configurations +func (c *configCache) prepareBatchRequests() contractreader.ExtendedBatchGetLatestValuesRequest { + var ( + nativeTokenAddress cciptypes.Bytes + onrampDynamicConfig getOnRampDynamicConfigResponse + commitLatestOCRConfig cciptypes.OCRConfigResponse + execLatestOCRConfig cciptypes.OCRConfigResponse + staticConfig offRampStaticChainConfig + dynamicConfig offRampDynamicChainConfig + selectorsAndConf selectorsAndConfigs + rmnDigestHeader cciptypes.RMNDigestHeader + rmnVersionConfig versionedConfig + rmnRemoteAddress []byte + feeQuoterConfig feeQuoterStaticConfig + ) + + return contractreader.ExtendedBatchGetLatestValuesRequest{ + consts.ContractNameRouter: {{ + ReadName: consts.MethodNameRouterGetWrappedNative, + Params: map[string]any{}, + ReturnVal: &nativeTokenAddress, + }}, + consts.ContractNameOnRamp: {{ + ReadName: consts.MethodNameOnRampGetDynamicConfig, + Params: map[string]any{}, + ReturnVal: &onrampDynamicConfig, + }}, + consts.ContractNameOffRamp: { + { + ReadName: consts.MethodNameOffRampLatestConfigDetails, + Params: map[string]any{ + "ocrPluginType": consts.PluginTypeCommit, + }, + ReturnVal: &commitLatestOCRConfig, + }, + { + ReadName: consts.MethodNameOffRampLatestConfigDetails, + Params: map[string]any{ + "ocrPluginType": consts.PluginTypeExecute, + }, + ReturnVal: &execLatestOCRConfig, + }, + { + ReadName: consts.MethodNameOffRampGetStaticConfig, + Params: map[string]any{}, + ReturnVal: &staticConfig, + }, + { + ReadName: consts.MethodNameOffRampGetDynamicConfig, + Params: map[string]any{}, + ReturnVal: &dynamicConfig, + }, + { + ReadName: consts.MethodNameOffRampGetAllSourceChainConfigs, + Params: map[string]any{}, + ReturnVal: &selectorsAndConf, + }, + }, + consts.ContractNameRMNRemote: { + { + ReadName: consts.MethodNameGetReportDigestHeader, + Params: map[string]any{}, + ReturnVal: &rmnDigestHeader, + }, + { + ReadName: consts.MethodNameGetVersionedConfig, + Params: map[string]any{}, + ReturnVal: &rmnVersionConfig, + }, + }, + consts.ContractNameRMNProxy: {{ + ReadName: consts.MethodNameGetARM, + Params: map[string]any{}, + ReturnVal: &rmnRemoteAddress, + }}, + consts.ContractNameFeeQuoter: {{ + ReadName: consts.MethodNameFeeQuoterGetStaticConfig, + Params: map[string]any{}, + ReturnVal: &feeQuoterConfig, + }}, + } +} + +// updateFromResults updates the cache with results from the batch request +func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesResult) error { + for contract, results := range batchResult { + switch contract.Name { + case consts.ContractNameRouter: + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get router result: %w", err) + } + if typed, ok := val.(*cciptypes.Bytes); ok { + c.nativeTokenAddress = *typed + } + } + case consts.ContractNameOnRamp: + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get onramp result: %w", err) + } + if typed, ok := val.(*getOnRampDynamicConfigResponse); ok { + c.onrampDynamicConfig = *typed + } + } + case consts.ContractNameOffRamp: + for i, result := range results { + val, err := result.GetResult() + if err != nil { + return fmt.Errorf("get offramp result %d: %w", i, err) + } + switch i { + case 0: + if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.commitLatestOCRConfig = *typed + } + case 1: + if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.execLatestOCRConfig = *typed + } + case 2: + if typed, ok := val.(*offRampStaticChainConfig); ok { + c.offrampStaticConfig = *typed + } + case 3: + if typed, ok := val.(*offRampDynamicChainConfig); ok { + c.offrampDynamicConfig = *typed + } + case 4: + if typed, ok := val.(*selectorsAndConfigs); ok { + c.offrampAllChains = *typed + } + } + } + case consts.ContractNameRMNRemote: + for i, result := range results { + val, err := result.GetResult() + if err != nil { + return fmt.Errorf("get rmn remote result %d: %w", i, err) + } + switch i { + case 0: + if typed, ok := val.(*cciptypes.RMNDigestHeader); ok { + c.rmnDigestHeader = *typed + } + case 1: + if typed, ok := val.(*versionedConfig); ok { + c.rmnVersionedConfig = *typed + } + } + } + case consts.ContractNameRMNProxy: + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get rmn proxy result: %w", err) + } + if typed, ok := val.(*cciptypes.Bytes); ok { + c.rmnRemoteAddress = *typed + } + } + case consts.ContractNameFeeQuoter: + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get fee quoter result: %w", err) + } + if typed, ok := val.(*feeQuoterStaticConfig); ok { + c.feeQuoterConfig = *typed + } + } + } + } + return nil +} + +func (c *configCache) GetOffRampConfigDigest(ctx context.Context, pluginType uint8) ([32]byte, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return [32]byte{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + + if pluginType == consts.PluginTypeCommit { + return c.commitLatestOCRConfig.OCRConfig.ConfigInfo.ConfigDigest, nil + } + return c.execLatestOCRConfig.OCRConfig.ConfigInfo.ConfigDigest, nil +} + +// GetNativeTokenAddress returns the cached native token address +func (c *configCache) GetNativeTokenAddress(ctx context.Context) (cciptypes.Bytes, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return cciptypes.Bytes{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.nativeTokenAddress, nil +} + +// GetOnRampDynamicConfig returns the cached onramp dynamic config +func (c *configCache) GetOnRampDynamicConfig(ctx context.Context) (getOnRampDynamicConfigResponse, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return getOnRampDynamicConfigResponse{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.onrampDynamicConfig, nil +} + +// GetOffRampStaticConfig returns the cached offramp static config +func (c *configCache) GetOffRampStaticConfig(ctx context.Context) (offRampStaticChainConfig, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return offRampStaticChainConfig{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.offrampStaticConfig, nil +} + +// GetOffRampDynamicConfig returns the cached offramp dynamic config +func (c *configCache) GetOffRampDynamicConfig(ctx context.Context) (offRampDynamicChainConfig, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return offRampDynamicChainConfig{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.offrampDynamicConfig, nil +} + +// GetOffRampAllChains returns the cached offramp all chains config +func (c *configCache) GetOffRampAllChains(ctx context.Context) (selectorsAndConfigs, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return selectorsAndConfigs{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.offrampAllChains, nil +} + +// GetRMNDigestHeader returns the cached RMN digest header +func (c *configCache) GetRMNDigestHeader(ctx context.Context) (cciptypes.RMNDigestHeader, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return cciptypes.RMNDigestHeader{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.rmnDigestHeader, nil +} + +// GetRMNVersionedConfig returns the cached RMN versioned config +func (c *configCache) GetRMNVersionedConfig(ctx context.Context) (versionedConfig, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return versionedConfig{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.rmnVersionedConfig, nil +} + +// GetFeeQuoterConfig returns the cached fee quoter config +func (c *configCache) GetFeeQuoterConfig(ctx context.Context) (feeQuoterStaticConfig, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return feeQuoterStaticConfig{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.feeQuoterConfig, nil +} + +// GetRMNRemoteAddress returns the cached RMN remote address +func (c *configCache) GetRMNRemoteAddress(ctx context.Context) (cciptypes.Bytes, error) { + if err := c.refreshIfNeeded(ctx); err != nil { + return cciptypes.Bytes{}, fmt.Errorf("refresh cache: %w", err) + } + + c.cacheMu.RLock() + defer c.cacheMu.RUnlock() + return c.rmnRemoteAddress, nil +} diff --git a/pkg/types/ccipocr3/config_types.go b/pkg/types/ccipocr3/config_types.go new file mode 100644 index 000000000..80a1a8f52 --- /dev/null +++ b/pkg/types/ccipocr3/config_types.go @@ -0,0 +1,22 @@ +package ccipocr3 + +type OCRConfigResponse struct { + OCRConfig OCRConfig +} + +type OCRConfig struct { + ConfigInfo ConfigInfo + Signers [][]byte + Transmitters [][]byte +} + +type ConfigInfo struct { + ConfigDigest [32]byte + F uint8 + N uint8 + IsSignatureVerificationEnabled bool +} + +type RMNDigestHeader struct { + DigestHeader Bytes32 +} From 367693d5dcb9774409fca1c01a92ba8bdb5a9670 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 30 Jan 2025 10:25:21 +0400 Subject: [PATCH 02/24] update tests --- .mockery.yaml | 3 + mocks/pkg/reader/configcache/config_cacher.go | 605 ++++++++++++++++++ pkg/reader/ccip.go | 115 +--- pkg/reader/ccip_test.go | 320 ++++----- pkg/reader/{ => configcache}/config_cache.go | 97 ++- pkg/reader/configcache/config_cache_test.go | 388 +++++++++++ pkg/types/ccipocr3/config_types.go | 95 +++ 7 files changed, 1349 insertions(+), 274 deletions(-) create mode 100644 mocks/pkg/reader/configcache/config_cacher.go rename pkg/reader/{ => configcache}/config_cache.go (73%) create mode 100644 pkg/reader/configcache/config_cache_test.go diff --git a/.mockery.yaml b/.mockery.yaml index c379cd190..5cc20faf3 100644 --- a/.mockery.yaml +++ b/.mockery.yaml @@ -22,6 +22,9 @@ packages: CCIPReader: PriceReader: RMNHome: + github.com/smartcontractkit/chainlink-ccip/pkg/reader/configcache: + interfaces: + ConfigCacher: github.com/smartcontractkit/chainlink-ccip/pkg/contractreader: interfaces: Extended: diff --git a/mocks/pkg/reader/configcache/config_cacher.go b/mocks/pkg/reader/configcache/config_cacher.go new file mode 100644 index 000000000..36ebe84b0 --- /dev/null +++ b/mocks/pkg/reader/configcache/config_cacher.go @@ -0,0 +1,605 @@ +// Code generated by mockery v2.43.2. DO NOT EDIT. + +package configcache + +import ( + ccipocr3 "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + + context "context" + + mock "github.com/stretchr/testify/mock" +) + +// MockConfigCacher is an autogenerated mock type for the ConfigCacher type +type MockConfigCacher struct { + mock.Mock +} + +type MockConfigCacher_Expecter struct { + mock *mock.Mock +} + +func (_m *MockConfigCacher) EXPECT() *MockConfigCacher_Expecter { + return &MockConfigCacher_Expecter{mock: &_m.Mock} +} + +// GetFeeQuoterConfig provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetFeeQuoterConfig(ctx context.Context) (ccipocr3.FeeQuoterStaticConfig, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetFeeQuoterConfig") + } + + var r0 ccipocr3.FeeQuoterStaticConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.FeeQuoterStaticConfig, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.FeeQuoterStaticConfig); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.FeeQuoterStaticConfig) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetFeeQuoterConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetFeeQuoterConfig' +type MockConfigCacher_GetFeeQuoterConfig_Call struct { + *mock.Call +} + +// GetFeeQuoterConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetFeeQuoterConfig(ctx interface{}) *MockConfigCacher_GetFeeQuoterConfig_Call { + return &MockConfigCacher_GetFeeQuoterConfig_Call{Call: _e.mock.On("GetFeeQuoterConfig", ctx)} +} + +func (_c *MockConfigCacher_GetFeeQuoterConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetFeeQuoterConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetFeeQuoterConfig_Call) Return(_a0 ccipocr3.FeeQuoterStaticConfig, _a1 error) *MockConfigCacher_GetFeeQuoterConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetFeeQuoterConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.FeeQuoterStaticConfig, error)) *MockConfigCacher_GetFeeQuoterConfig_Call { + _c.Call.Return(run) + return _c +} + +// GetNativeTokenAddress provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetNativeTokenAddress(ctx context.Context) (ccipocr3.Bytes, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetNativeTokenAddress") + } + + var r0 ccipocr3.Bytes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.Bytes, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.Bytes); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ccipocr3.Bytes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetNativeTokenAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetNativeTokenAddress' +type MockConfigCacher_GetNativeTokenAddress_Call struct { + *mock.Call +} + +// GetNativeTokenAddress is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetNativeTokenAddress(ctx interface{}) *MockConfigCacher_GetNativeTokenAddress_Call { + return &MockConfigCacher_GetNativeTokenAddress_Call{Call: _e.mock.On("GetNativeTokenAddress", ctx)} +} + +func (_c *MockConfigCacher_GetNativeTokenAddress_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetNativeTokenAddress_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetNativeTokenAddress_Call) Return(_a0 ccipocr3.Bytes, _a1 error) *MockConfigCacher_GetNativeTokenAddress_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetNativeTokenAddress_Call) RunAndReturn(run func(context.Context) (ccipocr3.Bytes, error)) *MockConfigCacher_GetNativeTokenAddress_Call { + _c.Call.Return(run) + return _c +} + +// GetOffRampAllChains provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetOffRampAllChains(ctx context.Context) (ccipocr3.SelectorsAndConfigs, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOffRampAllChains") + } + + var r0 ccipocr3.SelectorsAndConfigs + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.SelectorsAndConfigs, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.SelectorsAndConfigs); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.SelectorsAndConfigs) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetOffRampAllChains_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOffRampAllChains' +type MockConfigCacher_GetOffRampAllChains_Call struct { + *mock.Call +} + +// GetOffRampAllChains is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetOffRampAllChains(ctx interface{}) *MockConfigCacher_GetOffRampAllChains_Call { + return &MockConfigCacher_GetOffRampAllChains_Call{Call: _e.mock.On("GetOffRampAllChains", ctx)} +} + +func (_c *MockConfigCacher_GetOffRampAllChains_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetOffRampAllChains_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetOffRampAllChains_Call) Return(_a0 ccipocr3.SelectorsAndConfigs, _a1 error) *MockConfigCacher_GetOffRampAllChains_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetOffRampAllChains_Call) RunAndReturn(run func(context.Context) (ccipocr3.SelectorsAndConfigs, error)) *MockConfigCacher_GetOffRampAllChains_Call { + _c.Call.Return(run) + return _c +} + +// GetOffRampConfigDigest provides a mock function with given fields: ctx, pluginType +func (_m *MockConfigCacher) GetOffRampConfigDigest(ctx context.Context, pluginType uint8) ([32]byte, error) { + ret := _m.Called(ctx, pluginType) + + if len(ret) == 0 { + panic("no return value specified for GetOffRampConfigDigest") + } + + var r0 [32]byte + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, uint8) ([32]byte, error)); ok { + return rf(ctx, pluginType) + } + if rf, ok := ret.Get(0).(func(context.Context, uint8) [32]byte); ok { + r0 = rf(ctx, pluginType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([32]byte) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, uint8) error); ok { + r1 = rf(ctx, pluginType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetOffRampConfigDigest_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOffRampConfigDigest' +type MockConfigCacher_GetOffRampConfigDigest_Call struct { + *mock.Call +} + +// GetOffRampConfigDigest is a helper method to define mock.On call +// - ctx context.Context +// - pluginType uint8 +func (_e *MockConfigCacher_Expecter) GetOffRampConfigDigest(ctx interface{}, pluginType interface{}) *MockConfigCacher_GetOffRampConfigDigest_Call { + return &MockConfigCacher_GetOffRampConfigDigest_Call{Call: _e.mock.On("GetOffRampConfigDigest", ctx, pluginType)} +} + +func (_c *MockConfigCacher_GetOffRampConfigDigest_Call) Run(run func(ctx context.Context, pluginType uint8)) *MockConfigCacher_GetOffRampConfigDigest_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(uint8)) + }) + return _c +} + +func (_c *MockConfigCacher_GetOffRampConfigDigest_Call) Return(_a0 [32]byte, _a1 error) *MockConfigCacher_GetOffRampConfigDigest_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetOffRampConfigDigest_Call) RunAndReturn(run func(context.Context, uint8) ([32]byte, error)) *MockConfigCacher_GetOffRampConfigDigest_Call { + _c.Call.Return(run) + return _c +} + +// GetOffRampDynamicConfig provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetOffRampDynamicConfig(ctx context.Context) (ccipocr3.OffRampDynamicChainConfig, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOffRampDynamicConfig") + } + + var r0 ccipocr3.OffRampDynamicChainConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.OffRampDynamicChainConfig, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.OffRampDynamicChainConfig); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.OffRampDynamicChainConfig) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetOffRampDynamicConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOffRampDynamicConfig' +type MockConfigCacher_GetOffRampDynamicConfig_Call struct { + *mock.Call +} + +// GetOffRampDynamicConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetOffRampDynamicConfig(ctx interface{}) *MockConfigCacher_GetOffRampDynamicConfig_Call { + return &MockConfigCacher_GetOffRampDynamicConfig_Call{Call: _e.mock.On("GetOffRampDynamicConfig", ctx)} +} + +func (_c *MockConfigCacher_GetOffRampDynamicConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetOffRampDynamicConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetOffRampDynamicConfig_Call) Return(_a0 ccipocr3.OffRampDynamicChainConfig, _a1 error) *MockConfigCacher_GetOffRampDynamicConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetOffRampDynamicConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.OffRampDynamicChainConfig, error)) *MockConfigCacher_GetOffRampDynamicConfig_Call { + _c.Call.Return(run) + return _c +} + +// GetOffRampStaticConfig provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetOffRampStaticConfig(ctx context.Context) (ccipocr3.OffRampStaticChainConfig, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOffRampStaticConfig") + } + + var r0 ccipocr3.OffRampStaticChainConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.OffRampStaticChainConfig, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.OffRampStaticChainConfig); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.OffRampStaticChainConfig) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetOffRampStaticConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOffRampStaticConfig' +type MockConfigCacher_GetOffRampStaticConfig_Call struct { + *mock.Call +} + +// GetOffRampStaticConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetOffRampStaticConfig(ctx interface{}) *MockConfigCacher_GetOffRampStaticConfig_Call { + return &MockConfigCacher_GetOffRampStaticConfig_Call{Call: _e.mock.On("GetOffRampStaticConfig", ctx)} +} + +func (_c *MockConfigCacher_GetOffRampStaticConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetOffRampStaticConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetOffRampStaticConfig_Call) Return(_a0 ccipocr3.OffRampStaticChainConfig, _a1 error) *MockConfigCacher_GetOffRampStaticConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetOffRampStaticConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.OffRampStaticChainConfig, error)) *MockConfigCacher_GetOffRampStaticConfig_Call { + _c.Call.Return(run) + return _c +} + +// GetOnRampDynamicConfig provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetOnRampDynamicConfig(ctx context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetOnRampDynamicConfig") + } + + var r0 ccipocr3.GetOnRampDynamicConfigResponse + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.GetOnRampDynamicConfigResponse); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.GetOnRampDynamicConfigResponse) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetOnRampDynamicConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOnRampDynamicConfig' +type MockConfigCacher_GetOnRampDynamicConfig_Call struct { + *mock.Call +} + +// GetOnRampDynamicConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetOnRampDynamicConfig(ctx interface{}) *MockConfigCacher_GetOnRampDynamicConfig_Call { + return &MockConfigCacher_GetOnRampDynamicConfig_Call{Call: _e.mock.On("GetOnRampDynamicConfig", ctx)} +} + +func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetOnRampDynamicConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) Return(_a0 ccipocr3.GetOnRampDynamicConfigResponse, _a1 error) *MockConfigCacher_GetOnRampDynamicConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error)) *MockConfigCacher_GetOnRampDynamicConfig_Call { + _c.Call.Return(run) + return _c +} + +// GetRMNDigestHeader provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetRMNDigestHeader(ctx context.Context) (ccipocr3.RMNDigestHeader, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRMNDigestHeader") + } + + var r0 ccipocr3.RMNDigestHeader + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.RMNDigestHeader, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.RMNDigestHeader); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.RMNDigestHeader) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetRMNDigestHeader_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRMNDigestHeader' +type MockConfigCacher_GetRMNDigestHeader_Call struct { + *mock.Call +} + +// GetRMNDigestHeader is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetRMNDigestHeader(ctx interface{}) *MockConfigCacher_GetRMNDigestHeader_Call { + return &MockConfigCacher_GetRMNDigestHeader_Call{Call: _e.mock.On("GetRMNDigestHeader", ctx)} +} + +func (_c *MockConfigCacher_GetRMNDigestHeader_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetRMNDigestHeader_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetRMNDigestHeader_Call) Return(_a0 ccipocr3.RMNDigestHeader, _a1 error) *MockConfigCacher_GetRMNDigestHeader_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetRMNDigestHeader_Call) RunAndReturn(run func(context.Context) (ccipocr3.RMNDigestHeader, error)) *MockConfigCacher_GetRMNDigestHeader_Call { + _c.Call.Return(run) + return _c +} + +// GetRMNRemoteAddress provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetRMNRemoteAddress(ctx context.Context) (ccipocr3.Bytes, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRMNRemoteAddress") + } + + var r0 ccipocr3.Bytes + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.Bytes, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.Bytes); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(ccipocr3.Bytes) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetRMNRemoteAddress_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRMNRemoteAddress' +type MockConfigCacher_GetRMNRemoteAddress_Call struct { + *mock.Call +} + +// GetRMNRemoteAddress is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetRMNRemoteAddress(ctx interface{}) *MockConfigCacher_GetRMNRemoteAddress_Call { + return &MockConfigCacher_GetRMNRemoteAddress_Call{Call: _e.mock.On("GetRMNRemoteAddress", ctx)} +} + +func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetRMNRemoteAddress_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) Return(_a0 ccipocr3.Bytes, _a1 error) *MockConfigCacher_GetRMNRemoteAddress_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) RunAndReturn(run func(context.Context) (ccipocr3.Bytes, error)) *MockConfigCacher_GetRMNRemoteAddress_Call { + _c.Call.Return(run) + return _c +} + +// GetRMNVersionedConfig provides a mock function with given fields: ctx +func (_m *MockConfigCacher) GetRMNVersionedConfig(ctx context.Context) (ccipocr3.VersionedConfigRemote, error) { + ret := _m.Called(ctx) + + if len(ret) == 0 { + panic("no return value specified for GetRMNVersionedConfig") + } + + var r0 ccipocr3.VersionedConfigRemote + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.VersionedConfigRemote, error)); ok { + return rf(ctx) + } + if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.VersionedConfigRemote); ok { + r0 = rf(ctx) + } else { + r0 = ret.Get(0).(ccipocr3.VersionedConfigRemote) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(ctx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockConfigCacher_GetRMNVersionedConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetRMNVersionedConfig' +type MockConfigCacher_GetRMNVersionedConfig_Call struct { + *mock.Call +} + +// GetRMNVersionedConfig is a helper method to define mock.On call +// - ctx context.Context +func (_e *MockConfigCacher_Expecter) GetRMNVersionedConfig(ctx interface{}) *MockConfigCacher_GetRMNVersionedConfig_Call { + return &MockConfigCacher_GetRMNVersionedConfig_Call{Call: _e.mock.On("GetRMNVersionedConfig", ctx)} +} + +func (_c *MockConfigCacher_GetRMNVersionedConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetRMNVersionedConfig_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context)) + }) + return _c +} + +func (_c *MockConfigCacher_GetRMNVersionedConfig_Call) Return(_a0 ccipocr3.VersionedConfigRemote, _a1 error) *MockConfigCacher_GetRMNVersionedConfig_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockConfigCacher_GetRMNVersionedConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.VersionedConfigRemote, error)) *MockConfigCacher_GetRMNVersionedConfig_Call { + _c.Call.Return(run) + return _c +} + +// NewMockConfigCacher creates a new instance of MockConfigCacher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockConfigCacher(t interface { + mock.TestingT + Cleanup(func()) +}) *MockConfigCacher { + mock := &MockConfigCacher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index cbfffb4b1..6209d9c78 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -29,6 +29,7 @@ import ( "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" "github.com/smartcontractkit/chainlink-ccip/pkg/logutil" + "github.com/smartcontractkit/chainlink-ccip/pkg/reader/configcache" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" plugintypes2 "github.com/smartcontractkit/chainlink-ccip/plugintypes" ) @@ -42,7 +43,7 @@ type ccipChainReader struct { destChain cciptypes.ChainSelector offrampAddress string extraDataCodec cciptypes.ExtraDataCodec - caches map[cciptypes.ChainSelector]*configCache + caches map[cciptypes.ChainSelector]configcache.ConfigCacher } func newCCIPChainReaderInternal( @@ -66,11 +67,12 @@ func newCCIPChainReaderInternal( destChain: destChain, offrampAddress: typeconv.AddressBytesToString(offrampAddress, uint64(destChain)), extraDataCodec: extraDataCodec, + caches: make(map[cciptypes.ChainSelector]configcache.ConfigCacher), } // Initialize caches for each chain selector for chainSelector := range contractReaders { - reader.caches[chainSelector] = newConfigCache(reader) + reader.caches[chainSelector] = configcache.NewConfigCache(crs[chainSelector]) } contracts := ContractAddresses{ @@ -667,7 +669,9 @@ func (r *ccipChainReader) GetChainFeePriceUpdate(ctx context.Context, selectors return feeUpdates } -func (r *ccipChainReader) GetRMNRemoteConfig(ctx context.Context, destChainSelector cciptypes.ChainSelector) (rmntypes.RemoteConfig, error) { +func (r *ccipChainReader) GetRMNRemoteConfig( + ctx context.Context, + destChainSelector cciptypes.ChainSelector) (rmntypes.RemoteConfig, error) { if err := validateExtendedReaderExistence(r.contractReaders, destChainSelector); err != nil { return rmntypes.RemoteConfig{}, err } @@ -796,7 +800,7 @@ func (r *ccipChainReader) discoverOffRampContracts( var resp ContractAddresses - // OnRamps are in the offRamp SourceChainConfig. + // OnRamps are in the offRamp sourceChainConfig. { selectorsAndConfigs, err := cache.GetOffRampAllChains(ctx) if err != nil { @@ -958,22 +962,12 @@ func (r *ccipChainReader) LinkPriceUSD(ctx context.Context) (cciptypes.BigInt, e return linkPriceUSD, nil } -// feeQuoterStaticConfig is used to parse the response from the feeQuoter contract's getStaticConfig method. -// See: https://github.com/smartcontractkit/ccip/blob/a3f61f7458e4499c2c62eb38581c60b4942b1160/contracts/src/v0.8/ccip/FeeQuoter.sol#L946 -// -//nolint:lll // It's a URL. -type feeQuoterStaticConfig struct { - MaxFeeJuelsPerMsg cciptypes.BigInt `json:"maxFeeJuelsPerMsg"` - LinkToken []byte `json:"linkToken"` - StalenessThreshold uint32 `json:"stalenessThreshold"` -} - // getDestFeeQuoterStaticConfig returns the destination chain's Fee Quoter's StaticConfig -func (r *ccipChainReader) getDestFeeQuoterStaticConfig(ctx context.Context) (feeQuoterStaticConfig, error) { +func (r *ccipChainReader) getDestFeeQuoterStaticConfig(ctx context.Context) (cciptypes.FeeQuoterStaticConfig, error) { cache, ok := r.caches[r.destChain] if !ok { - return feeQuoterStaticConfig{}, fmt.Errorf("cache not found for chain %d", r.destChain) + return cciptypes.FeeQuoterStaticConfig{}, fmt.Errorf("cache not found for chain %d", r.destChain) } return cache.GetFeeQuoterConfig(ctx) @@ -1018,42 +1012,15 @@ func (r *ccipChainReader) getFeeQuoterTokenPriceUSD(ctx context.Context, tokenAd return cciptypes.NewBigInt(price), nil } -// sourceChainConfig is used to parse the response from the offRamp contract's getSourceChainConfig method. -// See: https://github.com/smartcontractkit/ccip/blob/a3f61f7458e4499c2c62eb38581c60b4942b1160/contracts/src/v0.8/ccip/offRamp/OffRamp.sol#L94 -// -//nolint:lll // It's a URL. -type sourceChainConfig struct { - Router []byte // local router - IsEnabled bool - MinSeqNr uint64 - OnRamp cciptypes.UnknownAddress -} - -func (scc sourceChainConfig) check() (bool /* enabled */, error) { - // The chain may be set in CCIPHome's ChainConfig map but not hooked up yet in the offramp. - if !scc.IsEnabled { - return false, nil - } - // This may happen due to some sort of regression in the codec that unmarshals - // chain data -> go struct. - if len(scc.OnRamp) == 0 { - return false, fmt.Errorf( - "onRamp misconfigured/didn't unmarshal: %x", - scc.OnRamp, - ) - } - return scc.IsEnabled, nil -} - // getOffRampSourceChainsConfig returns the offRamp contract's source chain configurations for each supported source // chain. If some chain is disabled it is not included in the response. func (r *ccipChainReader) getOffRampSourceChainsConfig( - ctx context.Context, chains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]sourceChainConfig, error) { + ctx context.Context, chains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]cciptypes.SourceChainConfig, error) { if err := validateExtendedReaderExistence(r.contractReaders, r.destChain); err != nil { return nil, err } - res := make(map[cciptypes.ChainSelector]sourceChainConfig) + res := make(map[cciptypes.ChainSelector]cciptypes.SourceChainConfig) mu := new(sync.Mutex) eg := new(errgroup.Group) @@ -1064,7 +1031,7 @@ func (r *ccipChainReader) getOffRampSourceChainsConfig( // TODO: look into using BatchGetLatestValue instead to simplify concurrency? eg.Go(func() error { - resp := sourceChainConfig{} + resp := cciptypes.SourceChainConfig{} err := r.contractReaders[r.destChain].ExtendedGetLatestValue( ctx, consts.ContractNameOffRamp, @@ -1080,7 +1047,7 @@ func (r *ccipChainReader) getOffRampSourceChainsConfig( chainSel, err) } - enabled, err := resp.check() + enabled, err := resp.Check() if err != nil { return fmt.Errorf("source chain config check for chain %d failed: %w", chainSel, err) } @@ -1103,18 +1070,12 @@ func (r *ccipChainReader) getOffRampSourceChainsConfig( return res, nil } -// selectorsAndConfigs wraps the return values from getAllSourceChainConfigs. -type selectorsAndConfigs struct { - Selectors []uint64 `mapstructure:"F0"` - SourceChainConfigs []sourceChainConfig `mapstructure:"F1"` -} - // getAllOffRampSourceChainsConfig get all enabled source chain configs from the offRamp for the provided chain. func (r *ccipChainReader) getAllOffRampSourceChainsConfig( ctx context.Context, lggr logger.Logger, chain cciptypes.ChainSelector, -) (map[cciptypes.ChainSelector]sourceChainConfig, error) { +) (map[cciptypes.ChainSelector]cciptypes.SourceChainConfig, error) { cache, ok := r.caches[chain] if !ok { return nil, fmt.Errorf("cache not found for chain selector %d", chain) @@ -1131,14 +1092,14 @@ func (r *ccipChainReader) getAllOffRampSourceChainsConfig( lggr.Debugw("got source chain configs", "configs", resp) - enabledConfigs := make(map[cciptypes.ChainSelector]sourceChainConfig) + enabledConfigs := make(map[cciptypes.ChainSelector]cciptypes.SourceChainConfig) // Populate the map and filter out disabled chains for i := range resp.Selectors { chainSel := cciptypes.ChainSelector(resp.Selectors[i]) cfg := resp.SourceChainConfigs[i] - enabled, err := cfg.check() + enabled, err := cfg.Check() if err != nil { return nil, fmt.Errorf("source chain config check for chain %d failed: %w", chainSel, err) } @@ -1154,9 +1115,9 @@ func (r *ccipChainReader) getAllOffRampSourceChainsConfig( return enabledConfigs, nil } -// offRampStaticChainConfig is used to parse the response from the offRamp contract's getStaticConfig method. +// OffRampStaticChainConfig is used to parse the response from the offRamp contract's getStaticConfig method. // See: /contracts/src/v0.8/ccip/offRamp/OffRamp.sol:StaticConfig -type offRampStaticChainConfig struct { +type OffRampStaticChainConfig struct { ChainSelector cciptypes.ChainSelector `json:"chainSelector"` GasForCallExactCheck uint16 `json:"gasForCallExactCheck"` RmnRemote []byte `json:"rmnRemote"` @@ -1164,8 +1125,8 @@ type offRampStaticChainConfig struct { NonceManager []byte `json:"nonceManager"` } -// offRampDynamicChainConfig maps to DynamicConfig in OffRamp.sol -type offRampDynamicChainConfig struct { +// OffRampDynamicChainConfig maps to DynamicConfig in OffRamp.sol +type OffRampDynamicChainConfig struct { FeeQuoter []byte `json:"feeQuoter"` PermissionLessExecutionThresholdSeconds uint32 `json:"permissionLessExecutionThresholdSeconds"` IsRMNVerificationDisabled bool `json:"isRMNVerificationDisabled"` @@ -1207,20 +1168,12 @@ type onRampDynamicConfig struct { AllowListAdmin []byte `json:"allowListAdmin"` } -// We're wrapping the onRampDynamicConfig this way to map to on-chain return type which is a named struct -// https://github.com/smartcontractkit/chainlink/blob/12af1de88238e0e918177d6b5622070417f48adf/contracts/src/v0.8/ccip/onRamp/OnRamp.sol#L328 -// -//nolint:lll -type getOnRampDynamicConfigResponse struct { - DynamicConfig onRampDynamicConfig `json:"dynamicConfig"` -} - func (r *ccipChainReader) getOnRampDynamicConfigs( ctx context.Context, lggr logger.Logger, srcChains []cciptypes.ChainSelector, -) map[cciptypes.ChainSelector]getOnRampDynamicConfigResponse { - result := make(map[cciptypes.ChainSelector]getOnRampDynamicConfigResponse) +) map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse { + result := make(map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse) for _, chainSel := range srcChains { // no onramp for the destination chain @@ -1318,28 +1271,6 @@ func (r *ccipChainReader) getOnRampDestChainConfig( return result } -// signer is used to parse the response from the RMNRemote contract's getVersionedConfig method. -// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L42-L45 -type signer struct { - OnchainPublicKey []byte `json:"onchainPublicKey"` - NodeIndex uint64 `json:"nodeIndex"` -} - -// config is used to parse the response from the RMNRemote contract's getVersionedConfig method. -// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L49-L53 -type config struct { - RMNHomeContractConfigDigest cciptypes.Bytes32 `json:"rmnHomeContractConfigDigest"` - Signers []signer `json:"signers"` - F uint64 `json:"f"` // previously: MinSigners -} - -// versionedConfig is used to parse the response from the RMNRemote contract's getVersionedConfig method. -// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L167-L169 -type versionedConfig struct { - Version uint32 `json:"version"` - Config config `json:"config"` -} - // Get the DestChainConfig from the FeeQuoter contract on the given chain. func (r *ccipChainReader) getFeeQuoterDestChainConfig( ctx context.Context, diff --git a/pkg/reader/ccip_test.go b/pkg/reader/ccip_test.go index e14526266..e3291ac92 100644 --- a/pkg/reader/ccip_test.go +++ b/pkg/reader/ccip_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - "go.uber.org/zap/zapcore" "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/types/ccipocr3" @@ -22,12 +21,16 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types/query/primitives" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + zapcore "go.uber.org/zap/zapcore" + typeconv "github.com/smartcontractkit/chainlink-ccip/internal/libs/typeconv" "github.com/smartcontractkit/chainlink-ccip/internal/plugintypes" writer_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/chainlink_common" reader_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/contractreader" + configcache_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/reader/configcache" "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" + "github.com/smartcontractkit/chainlink-ccip/pkg/reader/configcache" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) @@ -61,7 +64,7 @@ func TestCCIPChainReader_getSourceChainsConfig(t *testing.T) { returnVal interface{}, ) { sourceChain := params.(map[string]any)["sourceChainSelector"].(cciptypes.ChainSelector) - v := returnVal.(*sourceChainConfig) + v := returnVal.(*cciptypes.SourceChainConfig) fromString, err := cciptypes.NewBytesFromString(fmt.Sprintf( "0x%d000000000000000000000000000000000000000", sourceChain), @@ -402,7 +405,7 @@ func addDestinationContractAssertions( map[string]any{}, mock.Anything, ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*offRampStaticChainConfig) + v := returnVal.(*cciptypes.OffRampStaticChainConfig) v.NonceManager = destNonceMgr v.RmnRemote = destRMNRemote })) @@ -415,7 +418,7 @@ func addDestinationContractAssertions( map[string]any{}, mock.Anything, ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*offRampDynamicChainConfig) + v := returnVal.(*cciptypes.OffRampDynamicChainConfig) v.FeeQuoter = destFeeQuoter })) } @@ -431,8 +434,6 @@ func TestCCIPChainReader_DiscoverContracts_HappyPath_Round1(t *testing.T) { destRMNRemote := []byte{0x4} destFeeQuoter := []byte{0x5} destRouter := []byte{0x6} - //srcRouters := []byte{0x7, 0x8} - //srcFeeQuoters := [2][]byte{{0x7}, {0x8}} // Build expected addresses. var expectedContractAddresses ContractAddresses @@ -447,47 +448,48 @@ func TestCCIPChainReader_DiscoverContracts_HappyPath_Round1(t *testing.T) { expectedContractAddresses = expectedContractAddresses.Append(consts.ContractNameNonceManager, destChain, destNonceMgr) mockReaders := make(map[cciptypes.ChainSelector]*reader_mocks.MockExtended) + mockDestCache := configcache_mocks.NewMockConfigCacher(t) + mockSourceCaches := make(map[cciptypes.ChainSelector]*configcache_mocks.MockConfigCacher) mockReaders[destChain] = reader_mocks.NewMockExtended(t) - addDestinationContractAssertions(mockReaders[destChain], destNonceMgr, destRMNRemote, destFeeQuoter) - mockReaders[destChain].EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetAllSourceChainConfigs, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*selectorsAndConfigs) - v.Selectors = []uint64{uint64(sourceChain[0]), uint64(sourceChain[1])} - v.SourceChainConfigs = []sourceChainConfig{ - { - OnRamp: onramps[0], - Router: destRouter, - IsEnabled: true, - }, - { - OnRamp: onramps[1], - Router: destRouter, - IsEnabled: true, + // Mock destination chain config calls through cache + mockDestCache.EXPECT().GetOffRampAllChains(mock.Anything).Return( + cciptypes.SelectorsAndConfigs{ + Selectors: []uint64{uint64(sourceChain[0]), uint64(sourceChain[1])}, + SourceChainConfigs: []cciptypes.SourceChainConfig{ + { + OnRamp: onramps[0], + Router: destRouter, + IsEnabled: true, + }, + { + OnRamp: onramps[1], + Router: destRouter, + IsEnabled: true, + }, }, - } - })) + }, nil) + + mockDestCache.EXPECT().GetOffRampStaticConfig(mock.Anything).Return( + cciptypes.OffRampStaticChainConfig{ + NonceManager: destNonceMgr, + RmnRemote: destRMNRemote, + }, nil) + + mockDestCache.EXPECT().GetOffRampDynamicConfig(mock.Anything).Return( + cciptypes.OffRampDynamicChainConfig{ + FeeQuoter: destFeeQuoter, + }, nil) // mock calls to get fee quoter from onramps and source chain config from offramp. for _, selector := range sourceChain { mockReaders[selector] = reader_mocks.NewMockExtended(t) + mockSourceCaches[selector] = configcache_mocks.NewMockConfigCacher(t) // ErrNoBindings is ignored. - mockReaders[selector].EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOnRamp, - consts.MethodNameOnRampGetDynamicConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(contractreader.ErrNoBindings) + mockSourceCaches[selector].EXPECT().GetOnRampDynamicConfig(mock.Anything).Return( + cciptypes.GetOnRampDynamicConfigResponse{}, contractreader.ErrNoBindings) mockReaders[selector].EXPECT().ExtendedGetLatestValue( mock.Anything, @@ -512,6 +514,11 @@ func TestCCIPChainReader_DiscoverContracts_HappyPath_Round1(t *testing.T) { destChain: destChain, contractReaders: castToExtended, lggr: lggr, + caches: map[cciptypes.ChainSelector]configcache.ConfigCacher{ + destChain: mockDestCache, + sourceChain[0]: mockSourceCaches[sourceChain[0]], + sourceChain[1]: mockSourceCaches[sourceChain[1]], + }, } contractAddresses, err := ccipChainReader.DiscoverContracts(ctx) @@ -590,50 +597,54 @@ func TestCCIPChainReader_DiscoverContracts_HappyPath_Round2(t *testing.T) { expectedContractAddresses = expectedContractAddresses.Append(consts.ContractNameRouter, destChain, destRouter[0]) mockReaders := make(map[cciptypes.ChainSelector]*reader_mocks.MockExtended) + mockSourceCaches := make(map[cciptypes.ChainSelector]*configcache_mocks.MockConfigCacher) + // Setup destination chain mocks mockReaders[destChain] = reader_mocks.NewMockExtended(t) - addDestinationContractAssertions(mockReaders[destChain], destNonceMgr, destRMNRemote, destFeeQuoter) - - mockReaders[destChain].EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetAllSourceChainConfigs, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*selectorsAndConfigs) - v.Selectors = []uint64{uint64(sourceChain[0]), uint64(sourceChain[1])} - v.SourceChainConfigs = []sourceChainConfig{ - { - OnRamp: onramps[0], - Router: destRouter[0], - IsEnabled: true, - }, - { - OnRamp: onramps[1], - Router: destRouter[1], - IsEnabled: true, + mockDestCache := configcache_mocks.NewMockConfigCacher(t) + + // Mock destination chain config calls through cache + mockDestCache.EXPECT().GetOffRampAllChains(mock.Anything).Return( + cciptypes.SelectorsAndConfigs{ + Selectors: []uint64{uint64(sourceChain[0]), uint64(sourceChain[1])}, + SourceChainConfigs: []cciptypes.SourceChainConfig{ + { + OnRamp: onramps[0], + Router: destRouter[0], + IsEnabled: true, + }, + { + OnRamp: onramps[1], + Router: destRouter[1], + IsEnabled: true, + }, }, - } - })) + }, nil) - // mock calls to get fee quoter from onramps and source chain config from offramp. + mockDestCache.EXPECT().GetOffRampStaticConfig(mock.Anything).Return( + cciptypes.OffRampStaticChainConfig{ + NonceManager: destNonceMgr, + RmnRemote: destRMNRemote, + }, nil) + + mockDestCache.EXPECT().GetOffRampDynamicConfig(mock.Anything).Return( + cciptypes.OffRampDynamicChainConfig{ + FeeQuoter: destFeeQuoter, + }, nil) + + // Setup source chain mocks for i, selector := range sourceChain { mockReaders[selector] = reader_mocks.NewMockExtended(t) + mockSourceCaches[selector] = configcache_mocks.NewMockConfigCacher(t) - mockReaders[selector].EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOnRamp, - consts.MethodNameOnRampGetDynamicConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*getOnRampDynamicConfigResponse) - v.DynamicConfig.FeeQuoter = srcFeeQuoters[i] - })) + mockSourceCaches[selector].EXPECT().GetOnRampDynamicConfig(mock.Anything).Return( + cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: srcFeeQuoters[i], + }, + }, nil) + // Mock the router config through direct reader since it's not cached mockReaders[selector].EXPECT().ExtendedGetLatestValue( mock.Anything, consts.ContractNameOnRamp, @@ -654,11 +665,16 @@ func TestCCIPChainReader_DiscoverContracts_HappyPath_Round2(t *testing.T) { castToExtended[sel] = v } - // create the reader + // create the reader with caches ccipChainReader := &ccipChainReader{ destChain: destChain, contractReaders: castToExtended, lggr: logger.Test(t), + caches: map[cciptypes.ChainSelector]configcache.ConfigCacher{ + destChain: mockDestCache, + sourceChain[0]: mockSourceCaches[sourceChain[0]], + sourceChain[1]: mockSourceCaches[sourceChain[1]], + }, } contractAddresses, err := ccipChainReader.DiscoverContracts(ctx) @@ -674,16 +690,13 @@ func TestCCIPChainReader_DiscoverContracts_GetAllSourceChainConfig_Errors(t *tes sourceChain2 := cciptypes.ChainSelector(3) destExtended := reader_mocks.NewMockExtended(t) - // mock the call for sourceChain2 - failure + // Create mock cache but maintain same error behavior + mockCache := configcache_mocks.NewMockConfigCacher(t) + + // mock the call for sourceChain2 - failure, maintaining same error getLatestValueErr := errors.New("some error") - destExtended.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetAllSourceChainConfigs, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(getLatestValueErr) + mockCache.EXPECT().GetOffRampAllChains(mock.Anything).Return( + cciptypes.SelectorsAndConfigs{}, getLatestValueErr) // get static config call won't occur because the source chain config call failed. @@ -698,6 +711,9 @@ func TestCCIPChainReader_DiscoverContracts_GetAllSourceChainConfig_Errors(t *tes sourceChain1: reader_mocks.NewMockExtended(t), sourceChain2: reader_mocks.NewMockExtended(t), }, + caches: map[cciptypes.ChainSelector]configcache.ConfigCacher{ + destChain: mockCache, + }, lggr: logger.Test(t), } @@ -713,37 +729,32 @@ func TestCCIPChainReader_DiscoverContracts_GetOfframpStaticConfig_Errors(t *test sourceChain2 := cciptypes.ChainSelector(3) destExtended := reader_mocks.NewMockExtended(t) - // mock the call for source chain configs - destExtended.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetAllSourceChainConfigs, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil) // doesn't matter for this test - // mock the call to get the nonce manager - failure + // Create a mock cache for the destination chain + mockCache := configcache_mocks.NewMockConfigCacher(t) + + // Mock the call for source chain configs via cache + mockCache.EXPECT().GetOffRampAllChains(mock.Anything).Return( + cciptypes.SelectorsAndConfigs{ + Selectors: []uint64{}, + SourceChainConfigs: []cciptypes.SourceChainConfig{}, + }, nil) + + // Mock the call to get the static config - failure getLatestValueErr := errors.New("some error") - destExtended.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetStaticConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(getLatestValueErr) + mockCache.EXPECT().GetOffRampStaticConfig(mock.Anything).Return( + cciptypes.OffRampStaticChainConfig{}, getLatestValueErr) // create the reader ccipChainReader := &ccipChainReader{ destChain: destChain, contractReaders: map[cciptypes.ChainSelector]contractreader.Extended{ - destChain: destExtended, - // these won't be used in this test, but are needed because - // we determine the source chain selectors to query from the chains - // that we have readers for. + destChain: destExtended, sourceChain1: reader_mocks.NewMockExtended(t), sourceChain2: reader_mocks.NewMockExtended(t), }, + caches: map[cciptypes.ChainSelector]configcache.ConfigCacher{ + destChain: mockCache, + }, lggr: logger.Test(t), } @@ -772,29 +783,30 @@ func withReturnValueOverridden(mapper func(returnVal interface{})) func(ctx cont func TestCCIPChainReader_getDestFeeQuoterStaticConfig(t *testing.T) { destCR := reader_mocks.NewMockContractReaderFacade(t) - destCR.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) - destCR.EXPECT().HealthReport().Return(nil) - destCR.EXPECT().GetLatestValue( - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - mock.Anything, - ).Run(func( - ctx context.Context, - readIdentifier string, - confidenceLevel primitives.ConfidenceLevel, - params interface{}, - returnVal interface{}, - ) { - cfg := returnVal.(*feeQuoterStaticConfig) - cfg.MaxFeeJuelsPerMsg = cciptypes.NewBigIntFromInt64(10) - cfg.LinkToken = []byte{0x3, 0x4} - cfg.StalenessThreshold = 12 - }).Return(nil) + mockCache := configcache_mocks.NewMockConfigCacher(t) + + // Setup expected config + expectedConfig := cciptypes.FeeQuoterStaticConfig{ + MaxFeeJuelsPerMsg: cciptypes.NewBigIntFromInt64(10), + LinkToken: []byte{0x3, 0x4}, + StalenessThreshold: 12, + } offrampAddress := []byte{0x3} feeQuoterAddress := []byte{0x4} + + // Add expectation for the OffRamp bind that happens during initialization + destCR.EXPECT().Bind( + mock.Anything, + []types.BoundContract{{ + Name: consts.ContractNameOffRamp, + Address: typeconv.AddressBytesToString(offrampAddress, uint64(chainC)), + }}, + ).Return(nil) + + // Mock the cache to return the expected config + mockCache.EXPECT().GetFeeQuoterConfig(mock.Anything).Return(expectedConfig, nil).Once() + ccipReader := newCCIPChainReaderInternal( tests.Context(t), logger.Test(t), @@ -804,9 +816,25 @@ func TestCCIPChainReader_getDestFeeQuoterStaticConfig(t *testing.T) { ccipocr3.NewMockExtraDataCodec(t), ) + // Replace the automatically created cache with our mock + ccipReader.caches = map[cciptypes.ChainSelector]configcache.ConfigCacher{ + chainC: mockCache, + } + + // Add expectation for the FeeQuoter bind + destCR.EXPECT().Bind( + mock.Anything, + []types.BoundContract{{ + Name: "FeeQuoter", + Address: typeconv.AddressBytesToString(feeQuoterAddress, uint64(chainC)), + }}, + ).Return(nil) + require.NoError(t, ccipReader.contractReaders[chainC].Bind( - context.Background(), []types.BoundContract{{Name: "FeeQuoter", - Address: typeconv.AddressBytesToString(feeQuoterAddress, 111_111)}})) + context.Background(), []types.BoundContract{{ + Name: "FeeQuoter", + Address: typeconv.AddressBytesToString(feeQuoterAddress, uint64(chainC)), + }})) ctx := context.Background() cfg, err := ccipReader.getDestFeeQuoterStaticConfig(ctx) @@ -922,23 +950,17 @@ func TestCCIPFeeComponents_NotFoundErrors(t *testing.T) { func TestCCIPChainReader_LinkPriceUSD(t *testing.T) { tokenAddr := []byte{0x3, 0x4} destCR := reader_mocks.NewMockExtended(t) + mockCache := configcache_mocks.NewMockConfigCacher(t) destCR.EXPECT().Bind(mock.Anything, mock.Anything).Return(nil) - destCR.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameFeeQuoter, - consts.MethodNameFeeQuoterGetStaticConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - cfg := returnVal.(*feeQuoterStaticConfig) - cfg.MaxFeeJuelsPerMsg = cciptypes.NewBigIntFromInt64(10) - cfg.LinkToken = []byte{0x3, 0x4} - cfg.StalenessThreshold = 12 - })) + // Mock the config cache to return the FeeQuoter config + mockCache.EXPECT().GetFeeQuoterConfig(mock.Anything).Return(cciptypes.FeeQuoterStaticConfig{ + MaxFeeJuelsPerMsg: cciptypes.NewBigIntFromInt64(10), + LinkToken: tokenAddr, + StalenessThreshold: 12, + }, nil).Once() - // mock the call to get the fee quoter + // mock the call to get the fee quoter token price destCR.EXPECT().ExtendedGetLatestValue( mock.Anything, consts.ContractNameFeeQuoter, @@ -955,13 +977,17 @@ func TestCCIPChainReader_LinkPriceUSD(t *testing.T) { feeQuoterAddress := []byte{0x4} contractReaders := make(map[cciptypes.ChainSelector]contractreader.Extended) contractReaders[chainC] = destCR + ccipReader := ccipChainReader{ - logger.Test(t), - contractReaders, - nil, - chainC, - string(offrampAddress), - ccipocr3.NewMockExtraDataCodec(t), + lggr: logger.Test(t), + contractReaders: contractReaders, + contractWriters: nil, + destChain: chainC, + offrampAddress: string(offrampAddress), + extraDataCodec: ccipocr3.NewMockExtraDataCodec(t), + caches: map[cciptypes.ChainSelector]configcache.ConfigCacher{ + chainC: mockCache, + }, } require.NoError(t, ccipReader.contractReaders[chainC].Bind( diff --git a/pkg/reader/config_cache.go b/pkg/reader/configcache/config_cache.go similarity index 73% rename from pkg/reader/config_cache.go rename to pkg/reader/configcache/config_cache.go index 1f1592846..433322bdb 100644 --- a/pkg/reader/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -1,4 +1,4 @@ -package reader +package configcache import ( "context" @@ -16,9 +16,34 @@ const ( configCacheRefreshInterval = 30 * time.Second ) +// configCacher defines the interface for accessing cached config values +type ConfigCacher interface { + // OCR Config related methods + GetOffRampConfigDigest(ctx context.Context, pluginType uint8) ([32]byte, error) + + // Token related methods + GetNativeTokenAddress(ctx context.Context) (cciptypes.Bytes, error) + + // OnRamp related methods + GetOnRampDynamicConfig(ctx context.Context) (cciptypes.GetOnRampDynamicConfigResponse, error) + + // OffRamp related methods + GetOffRampStaticConfig(ctx context.Context) (cciptypes.OffRampStaticChainConfig, error) + GetOffRampDynamicConfig(ctx context.Context) (cciptypes.OffRampDynamicChainConfig, error) + GetOffRampAllChains(ctx context.Context) (cciptypes.SelectorsAndConfigs, error) + + // RMN related methods + GetRMNDigestHeader(ctx context.Context) (cciptypes.RMNDigestHeader, error) + GetRMNVersionedConfig(ctx context.Context) (cciptypes.VersionedConfigRemote, error) + GetRMNRemoteAddress(ctx context.Context) (cciptypes.Bytes, error) + + // FeeQuoter related methods + GetFeeQuoterConfig(ctx context.Context) (cciptypes.FeeQuoterStaticConfig, error) +} + // configCache handles caching of contract configurations with automatic refresh type configCache struct { - reader *ccipChainReader + reader contractreader.Extended cacheMu sync.RWMutex lastUpdateAt time.Time @@ -26,18 +51,18 @@ type configCache struct { nativeTokenAddress cciptypes.Bytes commitLatestOCRConfig cciptypes.OCRConfigResponse execLatestOCRConfig cciptypes.OCRConfigResponse - offrampStaticConfig offRampStaticChainConfig - offrampDynamicConfig offRampDynamicChainConfig - offrampAllChains selectorsAndConfigs - onrampDynamicConfig getOnRampDynamicConfigResponse + offrampStaticConfig cciptypes.OffRampStaticChainConfig + offrampDynamicConfig cciptypes.OffRampDynamicChainConfig + offrampAllChains cciptypes.SelectorsAndConfigs + onrampDynamicConfig cciptypes.GetOnRampDynamicConfigResponse rmnDigestHeader cciptypes.RMNDigestHeader - rmnVersionedConfig versionedConfig + rmnVersionedConfig cciptypes.VersionedConfigRemote rmnRemoteAddress cciptypes.Bytes - feeQuoterConfig feeQuoterStaticConfig + feeQuoterConfig cciptypes.FeeQuoterStaticConfig } -// newConfigCache creates a new instance of the configuration cache -func newConfigCache(reader *ccipChainReader) *configCache { +// NewConfigCache creates a new instance of the configuration cache +func NewConfigCache(reader contractreader.Extended) *configCache { return &configCache{ reader: reader, } @@ -64,7 +89,7 @@ func (c *configCache) refreshIfNeeded(ctx context.Context) error { func (c *configCache) refresh(ctx context.Context) error { requests := c.prepareBatchRequests() - batchResult, err := c.reader.contractReaders[c.reader.destChain].ExtendedBatchGetLatestValues(ctx, requests) + batchResult, err := c.reader.ExtendedBatchGetLatestValues(ctx, requests) if err != nil { return fmt.Errorf("batch get configs: %w", err) } @@ -80,16 +105,16 @@ func (c *configCache) refresh(ctx context.Context) error { func (c *configCache) prepareBatchRequests() contractreader.ExtendedBatchGetLatestValuesRequest { var ( nativeTokenAddress cciptypes.Bytes - onrampDynamicConfig getOnRampDynamicConfigResponse + onrampDynamicConfig cciptypes.GetOnRampDynamicConfigResponse commitLatestOCRConfig cciptypes.OCRConfigResponse execLatestOCRConfig cciptypes.OCRConfigResponse - staticConfig offRampStaticChainConfig - dynamicConfig offRampDynamicChainConfig - selectorsAndConf selectorsAndConfigs + staticConfig cciptypes.OffRampStaticChainConfig + dynamicConfig cciptypes.OffRampDynamicChainConfig + selectorsAndConf cciptypes.SelectorsAndConfigs rmnDigestHeader cciptypes.RMNDigestHeader - rmnVersionConfig versionedConfig + rmnVersionConfig cciptypes.VersionedConfigRemote rmnRemoteAddress []byte - feeQuoterConfig feeQuoterStaticConfig + feeQuoterConfig cciptypes.FeeQuoterStaticConfig ) return contractreader.ExtendedBatchGetLatestValuesRequest{ @@ -179,7 +204,7 @@ func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesRe if err != nil { return fmt.Errorf("get onramp result: %w", err) } - if typed, ok := val.(*getOnRampDynamicConfigResponse); ok { + if typed, ok := val.(*cciptypes.GetOnRampDynamicConfigResponse); ok { c.onrampDynamicConfig = *typed } } @@ -199,15 +224,15 @@ func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesRe c.execLatestOCRConfig = *typed } case 2: - if typed, ok := val.(*offRampStaticChainConfig); ok { + if typed, ok := val.(*cciptypes.OffRampStaticChainConfig); ok { c.offrampStaticConfig = *typed } case 3: - if typed, ok := val.(*offRampDynamicChainConfig); ok { + if typed, ok := val.(*cciptypes.OffRampDynamicChainConfig); ok { c.offrampDynamicConfig = *typed } case 4: - if typed, ok := val.(*selectorsAndConfigs); ok { + if typed, ok := val.(*cciptypes.SelectorsAndConfigs); ok { c.offrampAllChains = *typed } } @@ -224,7 +249,7 @@ func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesRe c.rmnDigestHeader = *typed } case 1: - if typed, ok := val.(*versionedConfig); ok { + if typed, ok := val.(*cciptypes.VersionedConfigRemote); ok { c.rmnVersionedConfig = *typed } } @@ -245,7 +270,7 @@ func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesRe if err != nil { return fmt.Errorf("get fee quoter result: %w", err) } - if typed, ok := val.(*feeQuoterStaticConfig); ok { + if typed, ok := val.(*cciptypes.FeeQuoterStaticConfig); ok { c.feeQuoterConfig = *typed } } @@ -280,9 +305,9 @@ func (c *configCache) GetNativeTokenAddress(ctx context.Context) (cciptypes.Byte } // GetOnRampDynamicConfig returns the cached onramp dynamic config -func (c *configCache) GetOnRampDynamicConfig(ctx context.Context) (getOnRampDynamicConfigResponse, error) { +func (c *configCache) GetOnRampDynamicConfig(ctx context.Context) (cciptypes.GetOnRampDynamicConfigResponse, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return getOnRampDynamicConfigResponse{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.GetOnRampDynamicConfigResponse{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -291,9 +316,9 @@ func (c *configCache) GetOnRampDynamicConfig(ctx context.Context) (getOnRampDyna } // GetOffRampStaticConfig returns the cached offramp static config -func (c *configCache) GetOffRampStaticConfig(ctx context.Context) (offRampStaticChainConfig, error) { +func (c *configCache) GetOffRampStaticConfig(ctx context.Context) (cciptypes.OffRampStaticChainConfig, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return offRampStaticChainConfig{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.OffRampStaticChainConfig{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -302,9 +327,9 @@ func (c *configCache) GetOffRampStaticConfig(ctx context.Context) (offRampStatic } // GetOffRampDynamicConfig returns the cached offramp dynamic config -func (c *configCache) GetOffRampDynamicConfig(ctx context.Context) (offRampDynamicChainConfig, error) { +func (c *configCache) GetOffRampDynamicConfig(ctx context.Context) (cciptypes.OffRampDynamicChainConfig, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return offRampDynamicChainConfig{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.OffRampDynamicChainConfig{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -313,9 +338,9 @@ func (c *configCache) GetOffRampDynamicConfig(ctx context.Context) (offRampDynam } // GetOffRampAllChains returns the cached offramp all chains config -func (c *configCache) GetOffRampAllChains(ctx context.Context) (selectorsAndConfigs, error) { +func (c *configCache) GetOffRampAllChains(ctx context.Context) (cciptypes.SelectorsAndConfigs, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return selectorsAndConfigs{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.SelectorsAndConfigs{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -335,9 +360,9 @@ func (c *configCache) GetRMNDigestHeader(ctx context.Context) (cciptypes.RMNDige } // GetRMNVersionedConfig returns the cached RMN versioned config -func (c *configCache) GetRMNVersionedConfig(ctx context.Context) (versionedConfig, error) { +func (c *configCache) GetRMNVersionedConfig(ctx context.Context) (cciptypes.VersionedConfigRemote, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return versionedConfig{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.VersionedConfigRemote{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -346,9 +371,9 @@ func (c *configCache) GetRMNVersionedConfig(ctx context.Context) (versionedConfi } // GetFeeQuoterConfig returns the cached fee quoter config -func (c *configCache) GetFeeQuoterConfig(ctx context.Context) (feeQuoterStaticConfig, error) { +func (c *configCache) GetFeeQuoterConfig(ctx context.Context) (cciptypes.FeeQuoterStaticConfig, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return feeQuoterStaticConfig{}, fmt.Errorf("refresh cache: %w", err) + return cciptypes.FeeQuoterStaticConfig{}, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock() @@ -366,3 +391,5 @@ func (c *configCache) GetRMNRemoteAddress(ctx context.Context) (cciptypes.Bytes, defer c.cacheMu.RUnlock() return c.rmnRemoteAddress, nil } + +var _ ConfigCacher = (*configCache)(nil) diff --git a/pkg/reader/configcache/config_cache_test.go b/pkg/reader/configcache/config_cache_test.go new file mode 100644 index 000000000..50099c699 --- /dev/null +++ b/pkg/reader/configcache/config_cache_test.go @@ -0,0 +1,388 @@ +package configcache + +import ( + "errors" + "math/big" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + + reader_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/contractreader" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" +) + +// createMockBatchResult creates a mock batch read result with the given return value +func createMockBatchResult(t *testing.T, retVal interface{}) types.BatchReadResult { + result := &types.BatchReadResult{} + result.SetResult(retVal, nil) + return *result +} + +func setupConfigCacheTest(t *testing.T) (*configCache, *reader_mocks.MockExtended) { + mockReader := reader_mocks.NewMockExtended(t) + + cache := NewConfigCache(mockReader) + + return cache, mockReader +} + +func TestConfigCache_Refresh(t *testing.T) { + t.Run("handles partial results in batch response", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // Only return some of the expected results + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.Bytes{1, 2, 3}), + }, + // Intentionally missing other contracts + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + err := cache.refresh(tests.Context(t)) + require.NoError(t, err) + + // Verify only router config was updated + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) + }) + + t.Run("handles type mismatch in results", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // Return wrong type in result + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(t, "wrong type"), // String instead of Bytes + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + err := cache.refresh(tests.Context(t)) + require.NoError(t, err) // Should not error but skip invalid result + }) +} + +func TestConfigCache_GetOffRampConfigDigest(t *testing.T) { + t.Run("returns commit config digest", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedDigest := [32]byte{1, 2, 3} + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.OCRConfigResponse{ + OCRConfig: cciptypes.OCRConfig{ + ConfigInfo: cciptypes.ConfigInfo{ + ConfigDigest: expectedDigest, + }, + }, + }), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + digest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeCommit) + require.NoError(t, err) + assert.Equal(t, expectedDigest, digest) + }) + + t.Run("returns execute config digest", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedDigest := [32]byte{4, 5, 6} + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // commit config + createMockBatchResult(t, &cciptypes.OCRConfigResponse{ // execute config + OCRConfig: cciptypes.OCRConfig{ + ConfigInfo: cciptypes.ConfigInfo{ + ConfigDigest: expectedDigest, + }, + }, + }), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + digest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeExecute) + require.NoError(t, err) + assert.Equal(t, expectedDigest, digest) + }) +} + +func TestConfigCache_GetRMNVersionedConfig(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedConfig := cciptypes.VersionedConfigRemote{ + Version: 1, + Config: cciptypes.Config{ + RMNHomeContractConfigDigest: [32]byte{1, 2, 3}, + Signers: []cciptypes.Signer{ + { + OnchainPublicKey: []byte{4, 5, 6}, + NodeIndex: 1, + }, + }, + F: 2, + }, + } + + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRMNRemote}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.RMNDigestHeader{}), // digest header + createMockBatchResult(t, &expectedConfig), // versioned config + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetRMNVersionedConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, expectedConfig, config) +} + +func TestConfigCache_GetOnRampDynamicConfig(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedConfig := cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: []byte{1, 2, 3}, + AllowListAdmin: []byte{4, 5, 6}, + MessageInterceptor: []byte{7, 8, 9}, + }, + } + + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ + createMockBatchResult(t, &expectedConfig), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetOnRampDynamicConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, expectedConfig, config) +} + +func TestConfigCache_GetOffRampAllChains(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedConfig := cciptypes.SelectorsAndConfigs{ + Selectors: []uint64{1, 2, 3}, + SourceChainConfigs: []cciptypes.SourceChainConfig{ + { + Router: []byte{1, 2, 3}, + IsEnabled: true, + MinSeqNr: 100, + }, + { + Router: []byte{4, 5, 6}, + IsEnabled: true, + MinSeqNr: 200, + }, + }, + } + + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // commit config + createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // execute config + createMockBatchResult(t, &cciptypes.OffRampStaticChainConfig{}), // static config + createMockBatchResult(t, &cciptypes.OffRampDynamicChainConfig{}), // dynamic config + createMockBatchResult(t, &expectedConfig), // all chains config + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetOffRampAllChains(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, expectedConfig, config) +} + +func TestConfigCache_ConcurrentAccess(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // Setup mock to always return some data + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(t, &cciptypes.Bytes{1, 2, 3}), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil).Maybe() + + // Run multiple goroutines accessing cache simultaneously + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + _, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + }() + } + wg.Wait() +} + +func TestConfigCache_UpdateFromResults(t *testing.T) { + t.Run("handles error getting result", func(t *testing.T) { + cache, _ := setupConfigCacheTest(t) + + // Create a mock BatchReadResult that returns an error + errResult := &types.BatchReadResult{} + errResult.SetResult(nil, errors.New("failed to get result")) + + results := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + *errResult, + }, + } + + err := cache.updateFromResults(results) + require.Error(t, err) + assert.Contains(t, err.Error(), "get router result") + }) + + t.Run("handles nil results", func(t *testing.T) { + cache, _ := setupConfigCacheTest(t) + + results := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(t, nil), + }, + } + + err := cache.updateFromResults(results) + require.NoError(t, err) + }) +} + +func TestConfigCache_GetFeeQuoterConfig(t *testing.T) { + t.Run("returns cached fee quoter config", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedConfig := cciptypes.FeeQuoterStaticConfig{ + MaxFeeJuelsPerMsg: cciptypes.BigInt{Int: big.NewInt(1000)}, + LinkToken: []byte{1, 2, 3, 4}, + StalenessThreshold: uint32(300), + } + + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ + createMockBatchResult(t, &expectedConfig), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetFeeQuoterConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, expectedConfig.MaxFeeJuelsPerMsg, config.MaxFeeJuelsPerMsg) + assert.Equal(t, expectedConfig.LinkToken, config.LinkToken) + assert.Equal(t, expectedConfig.StalenessThreshold, config.StalenessThreshold) + }) + + t.Run("handles invalid fee quoter config", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + invalidConfig := cciptypes.FeeQuoterStaticConfig{ + MaxFeeJuelsPerMsg: cciptypes.BigInt{Int: big.NewInt(0)}, // Invalid zero max fee + LinkToken: []byte{}, // Invalid empty address + StalenessThreshold: uint32(0), // Invalid zero threshold + } + + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ + createMockBatchResult(t, &invalidConfig), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetFeeQuoterConfig(tests.Context(t)) + require.NoError(t, err) // Should not error as validation is not part of the cache + assert.Equal(t, invalidConfig, config) + }) + + t.Run("handles missing fee quoter config", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // Return empty results + mockBatchResults := types.BatchGetLatestValuesResult{} + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + config, err := cache.GetFeeQuoterConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.FeeQuoterStaticConfig{}, config) // Should return empty config + }) +} + +func TestConfigCache_GetRMNRemoteAddress(t *testing.T) { + t.Run("returns cached RMN remote address", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + expectedAddress := cciptypes.Bytes{1, 2, 3, 4} + mockBatchResults := types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRMNProxy}: []types.BatchReadResult{ + createMockBatchResult(t, &expectedAddress), + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValues( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil) + + address, err := cache.GetRMNRemoteAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, expectedAddress, address) + }) +} diff --git a/pkg/types/ccipocr3/config_types.go b/pkg/types/ccipocr3/config_types.go index 80a1a8f52..70fa3e7f6 100644 --- a/pkg/types/ccipocr3/config_types.go +++ b/pkg/types/ccipocr3/config_types.go @@ -1,5 +1,9 @@ package ccipocr3 +import ( + "fmt" +) + type OCRConfigResponse struct { OCRConfig OCRConfig } @@ -20,3 +24,94 @@ type ConfigInfo struct { type RMNDigestHeader struct { DigestHeader Bytes32 } + +// FeeQuoterStaticConfig is used to parse the response from the feeQuoter contract's getStaticConfig method. +// See: https://github.com/smartcontractkit/ccip/blob/a3f61f7458e4499c2c62eb38581c60b4942b1160/contracts/src/v0.8/ccip/FeeQuoter.sol#L946 +// +//nolint:lll // It's a URL. +type FeeQuoterStaticConfig struct { + MaxFeeJuelsPerMsg BigInt + LinkToken []byte + StalenessThreshold uint32 +} + +// selectorsAndConfigs wraps the return values from getAllsourceChainConfigs. +type SelectorsAndConfigs struct { + Selectors []uint64 + SourceChainConfigs []SourceChainConfig +} + +// sourceChainConfig is used to parse the response from the offRamp contract's getSourceChainConfig method. +// See: https://github.com/smartcontractkit/ccip/blob/a3f61f7458e4499c2c62eb38581c60b4942b1160/contracts/src/v0.8/ccip/offRamp/OffRamp.sol#L94 +// +//nolint:lll // It's a URL. +type SourceChainConfig struct { + Router []byte + IsEnabled bool + MinSeqNr uint64 + OnRamp UnknownAddress +} + +func (scc SourceChainConfig) Check() (bool /* enabled */, error) { + // The chain may be set in CCIPHome's ChainConfig map but not hooked up yet in the offramp. + if !scc.IsEnabled { + return false, nil + } + // This may happen due to some sort of regression in the codec that unmarshals + // chain data -> go struct. + if len(scc.OnRamp) == 0 { + return false, fmt.Errorf( + "onRamp misconfigured/didn't unmarshal: %x", + scc.OnRamp, + ) + } + return scc.IsEnabled, nil +} + +type OffRampStaticChainConfig struct { + ChainSelector ChainSelector + GasForCallExactCheck uint16 + RmnRemote []byte + TokenAdminRegistry []byte + NonceManager []byte +} + +type OffRampDynamicChainConfig struct { + FeeQuoter []byte + PermissionLessExecutionThresholdSeconds uint32 + IsRMNVerificationDisabled bool + MessageInterceptor []byte +} +type GetOnRampDynamicConfigResponse struct { + DynamicConfig OnRampDynamicConfig +} + +type OnRampDynamicConfig struct { + FeeQuoter []byte + ReentrancyGuardEntered bool + MessageInterceptor []byte + FeeAggregator []byte + AllowListAdmin []byte +} + +// VersionedConfigRemote is used to parse the response from the RMNRemote contract's getVersionedConfig method. +// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L167-L169 +type VersionedConfigRemote struct { + Version uint32 + Config Config +} + +// config is used to parse the response from the RMNRemote contract's getVersionedConfig method. +// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L49-L53 +type Config struct { + RMNHomeContractConfigDigest Bytes32 `json:"rmnHomeContractConfigDigest"` + Signers []Signer `json:"signers"` + F uint64 `json:"f"` // previously: MinSigners +} + +// signer is used to parse the response from the RMNRemote contract's getVersionedConfig method. +// See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L42-L45 +type Signer struct { + OnchainPublicKey []byte `json:"onchainPublicKey"` + NodeIndex uint64 `json:"nodeIndex"` +} From 828746135bb6070417a89551ad71a56736955d07 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 30 Jan 2025 11:03:45 +0400 Subject: [PATCH 03/24] lint --- pkg/reader/ccip.go | 83 +------- pkg/reader/ccip_test.go | 31 --- pkg/reader/configcache/config_cache.go | 211 ++++++++++++-------- pkg/reader/configcache/config_cache_test.go | 40 ++-- pkg/types/ccipocr3/config_types.go | 11 +- 5 files changed, 156 insertions(+), 220 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 4c24a5bc9..b4e683a5d 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -1026,7 +1026,8 @@ func (r *ccipChainReader) getFeeQuoterTokenPriceUSD(ctx context.Context, tokenAd // getOffRampSourceChainsConfig returns the offRamp contract's source chain configurations for each supported source // chain. If some chain is disabled it is not included in the response. func (r *ccipChainReader) getOffRampSourceChainsConfig( - ctx context.Context, chains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]cciptypes.SourceChainConfig, error) { + ctx context.Context, + chains []cciptypes.ChainSelector) (map[cciptypes.ChainSelector]cciptypes.SourceChainConfig, error) { if err := validateExtendedReaderExistence(r.contractReaders, r.destChain); err != nil { return nil, err } @@ -1081,51 +1082,6 @@ func (r *ccipChainReader) getOffRampSourceChainsConfig( return res, nil } -// getAllOffRampSourceChainsConfig get all enabled source chain configs from the offRamp for the provided chain. -func (r *ccipChainReader) getAllOffRampSourceChainsConfig( - ctx context.Context, - lggr logger.Logger, - chain cciptypes.ChainSelector, -) (map[cciptypes.ChainSelector]cciptypes.SourceChainConfig, error) { - cache, ok := r.caches[chain] - if !ok { - return nil, fmt.Errorf("cache not found for chain selector %d", chain) - } - - resp, err := cache.GetOffRampAllChains(ctx) - if err != nil { - return nil, fmt.Errorf("failed to get source chain configs from cache for chain %d: %w", chain, err) - } - - if len(resp.SourceChainConfigs) != len(resp.Selectors) { - return nil, fmt.Errorf("selectors and source chain configs length mismatch: %v", resp) - } - - lggr.Debugw("got source chain configs", "configs", resp) - - enabledConfigs := make(map[cciptypes.ChainSelector]cciptypes.SourceChainConfig) - - // Populate the map and filter out disabled chains - for i := range resp.Selectors { - chainSel := cciptypes.ChainSelector(resp.Selectors[i]) - cfg := resp.SourceChainConfigs[i] - - enabled, err := cfg.Check() - if err != nil { - return nil, fmt.Errorf("source chain config check for chain %d failed: %w", chainSel, err) - } - if !enabled { - // We don't want to process disabled chains prematurely. - lggr.Debugw("source chain is disabled", "chain", chainSel) - continue - } - - enabledConfigs[chainSel] = cfg - } - - return enabledConfigs, nil -} - // OffRampStaticChainConfig is used to parse the response from the offRamp contract's getStaticConfig method. // See: /contracts/src/v0.8/ccip/offRamp/OffRamp.sol:StaticConfig type OffRampStaticChainConfig struct { @@ -1144,41 +1100,6 @@ type OffRampDynamicChainConfig struct { MessageInterceptor []byte `json:"messageInterceptor"` } -// getData returns data for a single reader. -func (r *ccipChainReader) getDestinationData( - ctx context.Context, - destChain cciptypes.ChainSelector, - contract string, - method string, - returnVal any, -) error { - if err := validateExtendedReaderExistence(r.contractReaders, destChain); err != nil { - return err - } - - if destChain != r.destChain { - return fmt.Errorf("expected destination chain %d, got %d", r.destChain, destChain) - } - - return r.contractReaders[destChain].ExtendedGetLatestValue( - ctx, - contract, - method, - primitives.Unconfirmed, - map[string]any{}, - returnVal, - ) -} - -// See DynamicChainConfig in OnRamp.sol -type onRampDynamicConfig struct { - FeeQuoter []byte `json:"feeQuoter"` - ReentrancyGuardEntered bool `json:"reentrancyGuardEntered"` - MessageInterceptor []byte `json:"messageInterceptor"` - FeeAggregator []byte `json:"feeAggregator"` - AllowListAdmin []byte `json:"allowListAdmin"` -} - func (r *ccipChainReader) getOnRampDynamicConfigs( ctx context.Context, lggr logger.Logger, diff --git a/pkg/reader/ccip_test.go b/pkg/reader/ccip_test.go index e3291ac92..9df8d2ab7 100644 --- a/pkg/reader/ccip_test.go +++ b/pkg/reader/ccip_test.go @@ -392,37 +392,6 @@ func TestCCIPChainReader_Sync_BindError(t *testing.T) { require.ErrorIs(t, err, expectedErr) } -func addDestinationContractAssertions( - extended *reader_mocks.MockExtended, - destNonceMgr, destRMNRemote, destFeeQuoter []byte, -) { - // mock the call to get the nonce manager - extended.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetStaticConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*cciptypes.OffRampStaticChainConfig) - v.NonceManager = destNonceMgr - v.RmnRemote = destRMNRemote - })) - // mock the call to get the fee quoter - extended.EXPECT().ExtendedGetLatestValue( - mock.Anything, - consts.ContractNameOffRamp, - consts.MethodNameOffRampGetDynamicConfig, - primitives.Unconfirmed, - map[string]any{}, - mock.Anything, - ).Return(nil).Run(withReturnValueOverridden(func(returnVal interface{}) { - v := returnVal.(*cciptypes.OffRampDynamicChainConfig) - v.FeeQuoter = destFeeQuoter - })) -} - // The round1 version returns NoBindingFound errors for onramp contracts to simulate // the two-phase approach to discovering those contracts. func TestCCIPChainReader_DiscoverContracts_HappyPath_Round1(t *testing.T) { diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 433322bdb..99cc2f958 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -6,10 +6,11 @@ import ( "sync" "time" + "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" - "github.com/smartcontractkit/chainlink-common/pkg/types" ) const ( @@ -62,7 +63,7 @@ type configCache struct { } // NewConfigCache creates a new instance of the configuration cache -func NewConfigCache(reader contractreader.Extended) *configCache { +func NewConfigCache(reader contractreader.Extended) ConfigCacher { return &configCache{ reader: reader, } @@ -187,93 +188,137 @@ func (c *configCache) prepareBatchRequests() contractreader.ExtendedBatchGetLate // updateFromResults updates the cache with results from the batch request func (c *configCache) updateFromResults(batchResult types.BatchGetLatestValuesResult) error { for contract, results := range batchResult { - switch contract.Name { - case consts.ContractNameRouter: - if len(results) > 0 { - val, err := results[0].GetResult() - if err != nil { - return fmt.Errorf("get router result: %w", err) - } - if typed, ok := val.(*cciptypes.Bytes); ok { - c.nativeTokenAddress = *typed - } + if err := c.handleContractResults(contract, results); err != nil { + return err + } + } + return nil +} + +// handleContractResults processes results for a specific contract +func (c *configCache) handleContractResults(contract types.BoundContract, results []types.BatchReadResult) error { + switch contract.Name { + case consts.ContractNameRouter: + return c.handleRouterResults(results) + case consts.ContractNameOnRamp: + return c.handleOnRampResults(results) + case consts.ContractNameOffRamp: + return c.handleOffRampResults(results) + case consts.ContractNameRMNRemote: + return c.handleRMNRemoteResults(results) + case consts.ContractNameRMNProxy: + return c.handleRMNProxyResults(results) + case consts.ContractNameFeeQuoter: + return c.handleFeeQuoterResults(results) + } + return nil +} + +// handleRouterResults processes router-specific results +func (c *configCache) handleRouterResults(results []types.BatchReadResult) error { + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get router result: %w", err) + } + if typed, ok := val.(*cciptypes.Bytes); ok { + c.nativeTokenAddress = *typed + } + } + return nil +} + +// handleOnRampResults processes onramp-specific results +func (c *configCache) handleOnRampResults(results []types.BatchReadResult) error { + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get onramp result: %w", err) + } + if typed, ok := val.(*cciptypes.GetOnRampDynamicConfigResponse); ok { + c.onrampDynamicConfig = *typed + } + } + return nil +} + +// handleOffRampResults processes offramp-specific results +func (c *configCache) handleOffRampResults(results []types.BatchReadResult) error { + for i, result := range results { + val, err := result.GetResult() + if err != nil { + return fmt.Errorf("get offramp result %d: %w", i, err) + } + switch i { + case 0: + if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.commitLatestOCRConfig = *typed } - case consts.ContractNameOnRamp: - if len(results) > 0 { - val, err := results[0].GetResult() - if err != nil { - return fmt.Errorf("get onramp result: %w", err) - } - if typed, ok := val.(*cciptypes.GetOnRampDynamicConfigResponse); ok { - c.onrampDynamicConfig = *typed - } + case 1: + if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.execLatestOCRConfig = *typed } - case consts.ContractNameOffRamp: - for i, result := range results { - val, err := result.GetResult() - if err != nil { - return fmt.Errorf("get offramp result %d: %w", i, err) - } - switch i { - case 0: - if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { - c.commitLatestOCRConfig = *typed - } - case 1: - if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { - c.execLatestOCRConfig = *typed - } - case 2: - if typed, ok := val.(*cciptypes.OffRampStaticChainConfig); ok { - c.offrampStaticConfig = *typed - } - case 3: - if typed, ok := val.(*cciptypes.OffRampDynamicChainConfig); ok { - c.offrampDynamicConfig = *typed - } - case 4: - if typed, ok := val.(*cciptypes.SelectorsAndConfigs); ok { - c.offrampAllChains = *typed - } - } + case 2: + if typed, ok := val.(*cciptypes.OffRampStaticChainConfig); ok { + c.offrampStaticConfig = *typed } - case consts.ContractNameRMNRemote: - for i, result := range results { - val, err := result.GetResult() - if err != nil { - return fmt.Errorf("get rmn remote result %d: %w", i, err) - } - switch i { - case 0: - if typed, ok := val.(*cciptypes.RMNDigestHeader); ok { - c.rmnDigestHeader = *typed - } - case 1: - if typed, ok := val.(*cciptypes.VersionedConfigRemote); ok { - c.rmnVersionedConfig = *typed - } - } + case 3: + if typed, ok := val.(*cciptypes.OffRampDynamicChainConfig); ok { + c.offrampDynamicConfig = *typed } - case consts.ContractNameRMNProxy: - if len(results) > 0 { - val, err := results[0].GetResult() - if err != nil { - return fmt.Errorf("get rmn proxy result: %w", err) - } - if typed, ok := val.(*cciptypes.Bytes); ok { - c.rmnRemoteAddress = *typed - } + case 4: + if typed, ok := val.(*cciptypes.SelectorsAndConfigs); ok { + c.offrampAllChains = *typed } - case consts.ContractNameFeeQuoter: - if len(results) > 0 { - val, err := results[0].GetResult() - if err != nil { - return fmt.Errorf("get fee quoter result: %w", err) - } - if typed, ok := val.(*cciptypes.FeeQuoterStaticConfig); ok { - c.feeQuoterConfig = *typed - } + } + } + return nil +} + +// handleRMNRemoteResults processes RMN remote-specific results +func (c *configCache) handleRMNRemoteResults(results []types.BatchReadResult) error { + for i, result := range results { + val, err := result.GetResult() + if err != nil { + return fmt.Errorf("get rmn remote result %d: %w", i, err) + } + switch i { + case 0: + if typed, ok := val.(*cciptypes.RMNDigestHeader); ok { + c.rmnDigestHeader = *typed } + case 1: + if typed, ok := val.(*cciptypes.VersionedConfigRemote); ok { + c.rmnVersionedConfig = *typed + } + } + } + return nil +} + +// handleRMNProxyResults processes RMN proxy-specific results +func (c *configCache) handleRMNProxyResults(results []types.BatchReadResult) error { + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get rmn proxy result: %w", err) + } + if typed, ok := val.(*cciptypes.Bytes); ok { + c.rmnRemoteAddress = *typed + } + } + return nil +} + +// handleFeeQuoterResults processes fee quoter-specific results +func (c *configCache) handleFeeQuoterResults(results []types.BatchReadResult) error { + if len(results) > 0 { + val, err := results[0].GetResult() + if err != nil { + return fmt.Errorf("get fee quoter result: %w", err) + } + if typed, ok := val.(*cciptypes.FeeQuoterStaticConfig); ok { + c.feeQuoterConfig = *typed } } return nil diff --git a/pkg/reader/configcache/config_cache_test.go b/pkg/reader/configcache/config_cache_test.go index 50099c699..dc75c0f38 100644 --- a/pkg/reader/configcache/config_cache_test.go +++ b/pkg/reader/configcache/config_cache_test.go @@ -19,7 +19,7 @@ import ( ) // createMockBatchResult creates a mock batch read result with the given return value -func createMockBatchResult(t *testing.T, retVal interface{}) types.BatchReadResult { +func createMockBatchResult(retVal interface{}) types.BatchReadResult { result := &types.BatchReadResult{} result.SetResult(retVal, nil) return *result @@ -28,7 +28,7 @@ func createMockBatchResult(t *testing.T, retVal interface{}) types.BatchReadResu func setupConfigCacheTest(t *testing.T) (*configCache, *reader_mocks.MockExtended) { mockReader := reader_mocks.NewMockExtended(t) - cache := NewConfigCache(mockReader) + cache := NewConfigCache(mockReader).(*configCache) return cache, mockReader } @@ -40,7 +40,7 @@ func TestConfigCache_Refresh(t *testing.T) { // Only return some of the expected results mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.Bytes{1, 2, 3}), + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), }, // Intentionally missing other contracts } @@ -65,7 +65,7 @@ func TestConfigCache_Refresh(t *testing.T) { // Return wrong type in result mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(t, "wrong type"), // String instead of Bytes + createMockBatchResult("wrong type"), // String instead of Bytes }, } @@ -86,7 +86,7 @@ func TestConfigCache_GetOffRampConfigDigest(t *testing.T) { expectedDigest := [32]byte{1, 2, 3} mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.OCRConfigResponse{ + createMockBatchResult(&cciptypes.OCRConfigResponse{ OCRConfig: cciptypes.OCRConfig{ ConfigInfo: cciptypes.ConfigInfo{ ConfigDigest: expectedDigest, @@ -112,8 +112,8 @@ func TestConfigCache_GetOffRampConfigDigest(t *testing.T) { expectedDigest := [32]byte{4, 5, 6} mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // commit config - createMockBatchResult(t, &cciptypes.OCRConfigResponse{ // execute config + createMockBatchResult(&cciptypes.OCRConfigResponse{}), // commit config + createMockBatchResult(&cciptypes.OCRConfigResponse{ // execute config OCRConfig: cciptypes.OCRConfig{ ConfigInfo: cciptypes.ConfigInfo{ ConfigDigest: expectedDigest, @@ -153,8 +153,8 @@ func TestConfigCache_GetRMNVersionedConfig(t *testing.T) { mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRMNRemote}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.RMNDigestHeader{}), // digest header - createMockBatchResult(t, &expectedConfig), // versioned config + createMockBatchResult(&cciptypes.RMNDigestHeader{}), // digest header + createMockBatchResult(&expectedConfig), // versioned config }, } @@ -181,7 +181,7 @@ func TestConfigCache_GetOnRampDynamicConfig(t *testing.T) { mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ - createMockBatchResult(t, &expectedConfig), + createMockBatchResult(&expectedConfig), }, } @@ -216,11 +216,11 @@ func TestConfigCache_GetOffRampAllChains(t *testing.T) { mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // commit config - createMockBatchResult(t, &cciptypes.OCRConfigResponse{}), // execute config - createMockBatchResult(t, &cciptypes.OffRampStaticChainConfig{}), // static config - createMockBatchResult(t, &cciptypes.OffRampDynamicChainConfig{}), // dynamic config - createMockBatchResult(t, &expectedConfig), // all chains config + createMockBatchResult(&cciptypes.OCRConfigResponse{}), // commit config + createMockBatchResult(&cciptypes.OCRConfigResponse{}), // execute config + createMockBatchResult(&cciptypes.OffRampStaticChainConfig{}), // static config + createMockBatchResult(&cciptypes.OffRampDynamicChainConfig{}), // dynamic config + createMockBatchResult(&expectedConfig), // all chains config }, } @@ -240,7 +240,7 @@ func TestConfigCache_ConcurrentAccess(t *testing.T) { // Setup mock to always return some data mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(t, &cciptypes.Bytes{1, 2, 3}), + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), }, } @@ -286,7 +286,7 @@ func TestConfigCache_UpdateFromResults(t *testing.T) { results := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(t, nil), + createMockBatchResult(nil), }, } @@ -307,7 +307,7 @@ func TestConfigCache_GetFeeQuoterConfig(t *testing.T) { mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ - createMockBatchResult(t, &expectedConfig), + createMockBatchResult(&expectedConfig), }, } @@ -334,7 +334,7 @@ func TestConfigCache_GetFeeQuoterConfig(t *testing.T) { mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ - createMockBatchResult(t, &invalidConfig), + createMockBatchResult(&invalidConfig), }, } @@ -372,7 +372,7 @@ func TestConfigCache_GetRMNRemoteAddress(t *testing.T) { expectedAddress := cciptypes.Bytes{1, 2, 3, 4} mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRMNProxy}: []types.BatchReadResult{ - createMockBatchResult(t, &expectedAddress), + createMockBatchResult(&expectedAddress), }, } diff --git a/pkg/types/ccipocr3/config_types.go b/pkg/types/ccipocr3/config_types.go index 70fa3e7f6..4ab07d7b8 100644 --- a/pkg/types/ccipocr3/config_types.go +++ b/pkg/types/ccipocr3/config_types.go @@ -86,12 +86,13 @@ type GetOnRampDynamicConfigResponse struct { DynamicConfig OnRampDynamicConfig } +// See DynamicChainConfig in OnRamp.sol type OnRampDynamicConfig struct { - FeeQuoter []byte - ReentrancyGuardEntered bool - MessageInterceptor []byte - FeeAggregator []byte - AllowListAdmin []byte + FeeQuoter []byte `json:"feeQuoter"` + ReentrancyGuardEntered bool `json:"reentrancyGuardEntered"` + MessageInterceptor []byte `json:"messageInterceptor"` + FeeAggregator []byte `json:"feeAggregator"` + AllowListAdmin []byte `json:"allowListAdmin"` } // VersionedConfigRemote is used to parse the response from the RMNRemote contract's getVersionedConfig method. From 49c0acdf651a82131dfa05018ed3f2be77b3d1da Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Thu, 30 Jan 2025 17:17:01 +0400 Subject: [PATCH 04/24] fix WithExtendedContractReader --- pkg/reader/ccip.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index b4e683a5d..5affe4076 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -71,8 +71,8 @@ func newCCIPChainReaderInternal( } // Initialize caches for each chain selector - for chainSelector := range contractReaders { - reader.caches[chainSelector] = configcache.NewConfigCache(crs[chainSelector]) + for chainSelector, cr := range crs { + reader.caches[chainSelector] = configcache.NewConfigCache(cr) } contracts := ContractAddresses{ @@ -91,6 +91,7 @@ func newCCIPChainReaderInternal( func (r *ccipChainReader) WithExtendedContractReader( ch cciptypes.ChainSelector, cr contractreader.Extended) *ccipChainReader { r.contractReaders[ch] = cr + r.caches[ch] = configcache.NewConfigCache(cr) return r } From fc0e7752b518fdb7f94792d1f72100a22044867a Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 09:44:20 +0400 Subject: [PATCH 05/24] introducing ExtendedBatchGetLatestValuesGraceful --- mocks/pkg/contractreader/extended.go | 57 ++ pkg/contractreader/extended.go | 66 ++ pkg/reader/ccip.go | 4 +- pkg/reader/configcache/config_cache.go | 42 +- pkg/reader/configcache/config_cache_test.go | 675 +++++++++++++------- 5 files changed, 594 insertions(+), 250 deletions(-) diff --git a/mocks/pkg/contractreader/extended.go b/mocks/pkg/contractreader/extended.go index 2b748854c..8db3ec991 100644 --- a/mocks/pkg/contractreader/extended.go +++ b/mocks/pkg/contractreader/extended.go @@ -193,6 +193,63 @@ func (_c *MockExtended_ExtendedBatchGetLatestValues_Call) RunAndReturn(run func( return _c } +// ExtendedBatchGetLatestValuesGraceful provides a mock function with given fields: ctx, request +func (_m *MockExtended) ExtendedBatchGetLatestValuesGraceful(ctx context.Context, request contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error) { + ret := _m.Called(ctx, request) + + if len(ret) == 0 { + panic("no return value specified for ExtendedBatchGetLatestValuesGraceful") + } + + var r0 contractreader.BatchGetLatestValuesGracefulResult + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error)); ok { + return rf(ctx, request) + } + if rf, ok := ret.Get(0).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) contractreader.BatchGetLatestValuesGracefulResult); ok { + r0 = rf(ctx, request) + } else { + r0 = ret.Get(0).(contractreader.BatchGetLatestValuesGracefulResult) + } + + if rf, ok := ret.Get(1).(func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) error); ok { + r1 = rf(ctx, request) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// MockExtended_ExtendedBatchGetLatestValuesGraceful_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ExtendedBatchGetLatestValuesGraceful' +type MockExtended_ExtendedBatchGetLatestValuesGraceful_Call struct { + *mock.Call +} + +// ExtendedBatchGetLatestValuesGraceful is a helper method to define mock.On call +// - ctx context.Context +// - request contractreader.ExtendedBatchGetLatestValuesRequest +func (_e *MockExtended_Expecter) ExtendedBatchGetLatestValuesGraceful(ctx interface{}, request interface{}) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + return &MockExtended_ExtendedBatchGetLatestValuesGraceful_Call{Call: _e.mock.On("ExtendedBatchGetLatestValuesGraceful", ctx, request)} +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) Run(run func(ctx context.Context, request contractreader.ExtendedBatchGetLatestValuesRequest)) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(contractreader.ExtendedBatchGetLatestValuesRequest)) + }) + return _c +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) Return(_a0 contractreader.BatchGetLatestValuesGracefulResult, _a1 error) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call) RunAndReturn(run func(context.Context, contractreader.ExtendedBatchGetLatestValuesRequest) (contractreader.BatchGetLatestValuesGracefulResult, error)) *MockExtended_ExtendedBatchGetLatestValuesGraceful_Call { + _c.Call.Return(run) + return _c +} + // ExtendedGetLatestValue provides a mock function with given fields: ctx, contractName, methodName, confidenceLevel, params, returnVal func (_m *MockExtended) ExtendedGetLatestValue(ctx context.Context, contractName string, methodName string, confidenceLevel primitives.ConfidenceLevel, params interface{}, returnVal interface{}) error { ret := _m.Called(ctx, contractName, methodName, confidenceLevel, params, returnVal) diff --git a/pkg/contractreader/extended.go b/pkg/contractreader/extended.go index 0e9390a82..9a3e82df8 100644 --- a/pkg/contractreader/extended.go +++ b/pkg/contractreader/extended.go @@ -84,6 +84,16 @@ type Extended interface { ctx context.Context, request ExtendedBatchGetLatestValuesRequest, ) (types.BatchGetLatestValuesResult, error) + + // ExtendedBatchGetLatestValuesGraceful performs the same operations as ExtendedBatchGetLatestValues + // but handles ErrNoBindings gracefully by: + // 1. Skipping contracts with no bindings instead of returning an error + // 2. Returning partial results for contracts that do have bindings + // 3. Including information about which contracts were skipped due to no bindings + ExtendedBatchGetLatestValuesGraceful( + ctx context.Context, + request ExtendedBatchGetLatestValuesRequest, + ) (BatchGetLatestValuesGracefulResult, error) } type ExtendedBatchGetLatestValuesRequest map[string]types.ContractBatch @@ -232,6 +242,62 @@ func (e *extendedContractReader) BatchGetLatestValues( return result, err } +// ExtendedBatchGetLatestValuesGraceful performs the same operations as ExtendedBatchGetLatestValues +// but handles ErrNoBindings gracefully by: +// 1. Skipping contracts with no bindings instead of returning an error +// 2. Returning partial results for contracts that do have bindings +// 3. Including information about which contracts were skipped due to no bindings +type BatchGetLatestValuesGracefulResult struct { + Results types.BatchGetLatestValuesResult + SkippedNoBinds []string // List of contract names that were skipped due to no bindings +} + +func (e *extendedContractReader) ExtendedBatchGetLatestValuesGraceful( + ctx context.Context, + request ExtendedBatchGetLatestValuesRequest, +) (BatchGetLatestValuesGracefulResult, error) { + // Convert the request from contract names to BoundContracts + convertedRequest := make(types.BatchGetLatestValuesRequest) + var skippedContracts []string + + for contractName, batch := range request { + // Get the binding for this contract name + binding, err := e.getOneBinding(contractName) + if err != nil { + if errors.Is(err, ErrNoBindings) { + // Track skipped contracts but continue processing + skippedContracts = append(skippedContracts, contractName) + continue + } + // Return error for any other binding issues + return BatchGetLatestValuesGracefulResult{}, fmt.Errorf( + "BatchGetLatestValuesGraceful: failed to get binding for contract %s: %w", contractName, err) + } + + // Use the resolved binding for the request + convertedRequest[binding.Binding] = batch + } + + // If we have no valid bindings after filtering, return empty result + if len(convertedRequest) == 0 { + return BatchGetLatestValuesGracefulResult{ + Results: make(types.BatchGetLatestValuesResult), + SkippedNoBinds: skippedContracts, + }, nil + } + + // Call the underlying BatchGetLatestValues with the converted request + results, err := e.BatchGetLatestValues(ctx, convertedRequest) + if err != nil { + return BatchGetLatestValuesGracefulResult{}, err + } + + return BatchGetLatestValuesGracefulResult{ + Results: results, + SkippedNoBinds: skippedContracts, + }, nil +} + func (e *extendedContractReader) ExtendedBatchGetLatestValues( ctx context.Context, request ExtendedBatchGetLatestValuesRequest, diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 5affe4076..4d507a21f 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -72,7 +72,7 @@ func newCCIPChainReaderInternal( // Initialize caches for each chain selector for chainSelector, cr := range crs { - reader.caches[chainSelector] = configcache.NewConfigCache(cr) + reader.caches[chainSelector] = configcache.NewConfigCache(cr, lggr) } contracts := ContractAddresses{ @@ -91,7 +91,7 @@ func newCCIPChainReaderInternal( func (r *ccipChainReader) WithExtendedContractReader( ch cciptypes.ChainSelector, cr contractreader.Extended) *ccipChainReader { r.contractReaders[ch] = cr - r.caches[ch] = configcache.NewConfigCache(cr) + r.caches[ch] = configcache.NewConfigCache(cr, r.lggr) return r } diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 99cc2f958..3060cdcb1 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -8,6 +8,8 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + "github.com/smartcontractkit/chainlink-ccip/pkg/consts" "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" @@ -45,6 +47,7 @@ type ConfigCacher interface { // configCache handles caching of contract configurations with automatic refresh type configCache struct { reader contractreader.Extended + lggr logger.Logger cacheMu sync.RWMutex lastUpdateAt time.Time @@ -63,9 +66,10 @@ type configCache struct { } // NewConfigCache creates a new instance of the configuration cache -func NewConfigCache(reader contractreader.Extended) ConfigCacher { +func NewConfigCache(reader contractreader.Extended, lggr logger.Logger) ConfigCacher { return &configCache{ reader: reader, + lggr: lggr, } } @@ -90,12 +94,19 @@ func (c *configCache) refreshIfNeeded(ctx context.Context) error { func (c *configCache) refresh(ctx context.Context) error { requests := c.prepareBatchRequests() - batchResult, err := c.reader.ExtendedBatchGetLatestValues(ctx, requests) + batchResult, err := c.reader.ExtendedBatchGetLatestValuesGraceful(ctx, requests) if err != nil { return fmt.Errorf("batch get configs: %w", err) } - if err := c.updateFromResults(batchResult); err != nil { + // Log skipped contracts if any for debugging + // Clear skipped contract values from cache + if len(batchResult.SkippedNoBinds) > 0 { + c.lggr.Infow("some contracts were skipped due to no bindings: %v", batchResult.SkippedNoBinds) + c.clearSkippedContractValues(batchResult.SkippedNoBinds) + } + + if err := c.updateFromResults(batchResult.Results); err != nil { return fmt.Errorf("update cache from results: %w", err) } @@ -324,6 +335,31 @@ func (c *configCache) handleFeeQuoterResults(results []types.BatchReadResult) er return nil } +// clearSkippedContractValues resets cache values for contracts that had no bindings +func (c *configCache) clearSkippedContractValues(skippedContracts []string) { + for _, contractName := range skippedContracts { + switch contractName { + case consts.ContractNameRouter: + c.nativeTokenAddress = cciptypes.Bytes{} + case consts.ContractNameOnRamp: + c.onrampDynamicConfig = cciptypes.GetOnRampDynamicConfigResponse{} + case consts.ContractNameOffRamp: + c.commitLatestOCRConfig = cciptypes.OCRConfigResponse{} + c.execLatestOCRConfig = cciptypes.OCRConfigResponse{} + c.offrampStaticConfig = cciptypes.OffRampStaticChainConfig{} + c.offrampDynamicConfig = cciptypes.OffRampDynamicChainConfig{} + c.offrampAllChains = cciptypes.SelectorsAndConfigs{} + case consts.ContractNameRMNRemote: + c.rmnDigestHeader = cciptypes.RMNDigestHeader{} + c.rmnVersionedConfig = cciptypes.VersionedConfigRemote{} + case consts.ContractNameRMNProxy: + c.rmnRemoteAddress = cciptypes.Bytes{} + case consts.ContractNameFeeQuoter: + c.feeQuoterConfig = cciptypes.FeeQuoterStaticConfig{} + } + } +} + func (c *configCache) GetOffRampConfigDigest(ctx context.Context, pluginType uint8) ([32]byte, error) { if err := c.refreshIfNeeded(ctx); err != nil { return [32]byte{}, fmt.Errorf("refresh cache: %w", err) diff --git a/pkg/reader/configcache/config_cache_test.go b/pkg/reader/configcache/config_cache_test.go index dc75c0f38..d8929a7d3 100644 --- a/pkg/reader/configcache/config_cache_test.go +++ b/pkg/reader/configcache/config_cache_test.go @@ -1,10 +1,12 @@ package configcache import ( - "errors" + "context" + "fmt" "math/big" "sync" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -13,8 +15,11 @@ import ( "github.com/smartcontractkit/chainlink-common/pkg/types" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" + "github.com/smartcontractkit/chainlink-common/pkg/logger" + reader_mocks "github.com/smartcontractkit/chainlink-ccip/mocks/pkg/contractreader" "github.com/smartcontractkit/chainlink-ccip/pkg/consts" + "github.com/smartcontractkit/chainlink-ccip/pkg/contractreader" cciptypes "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" ) @@ -28,361 +33,541 @@ func createMockBatchResult(retVal interface{}) types.BatchReadResult { func setupConfigCacheTest(t *testing.T) (*configCache, *reader_mocks.MockExtended) { mockReader := reader_mocks.NewMockExtended(t) - cache := NewConfigCache(mockReader).(*configCache) + testLogger := logger.Test(t) + + cache := NewConfigCache(mockReader, testLogger).(*configCache) return cache, mockReader } -func TestConfigCache_Refresh(t *testing.T) { - t.Run("handles partial results in batch response", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) +func TestConfigCache_Refresh_BasicScenario(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) - // Only return some of the expected results - mockBatchResults := types.BatchGetLatestValuesResult{ + // Create sample test data for all contract types + expectedResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), // native token address }, - // Intentionally missing other contracts - } - - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) - - err := cache.refresh(tests.Context(t)) - require.NoError(t, err) - - // Verify only router config was updated - addr, err := cache.GetNativeTokenAddress(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) - }) - - t.Run("handles type mismatch in results", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - // Return wrong type in result - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult("wrong type"), // String instead of Bytes + types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: []byte{4, 5, 6}, + ReentrancyGuardEntered: false, + MessageInterceptor: []byte{7, 8, 9}, + FeeAggregator: []byte{10, 11, 12}, + AllowListAdmin: []byte{13, 14, 15}, + }, + }), }, - } - - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) - - err := cache.refresh(tests.Context(t)) - require.NoError(t, err) // Should not error but skip invalid result - }) -} - -func TestConfigCache_GetOffRampConfigDigest(t *testing.T) { - t.Run("returns commit config digest", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - expectedDigest := [32]byte{1, 2, 3} - mockBatchResults := types.BatchGetLatestValuesResult{ types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.OCRConfigResponse{ + createMockBatchResult(&cciptypes.OCRConfigResponse{ // commit config OCRConfig: cciptypes.OCRConfig{ ConfigInfo: cciptypes.ConfigInfo{ - ConfigDigest: expectedDigest, + ConfigDigest: [32]byte{1}, + F: uint8(1), + N: uint8(3), + IsSignatureVerificationEnabled: true, }, + Signers: [][]byte{{1}, {2}, {3}}, + Transmitters: [][]byte{{4}, {5}, {6}}, }, }), - }, - } - - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) - - digest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeCommit) - require.NoError(t, err) - assert.Equal(t, expectedDigest, digest) - }) - - t.Run("returns execute config digest", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - expectedDigest := [32]byte{4, 5, 6} - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.OCRConfigResponse{}), // commit config createMockBatchResult(&cciptypes.OCRConfigResponse{ // execute config OCRConfig: cciptypes.OCRConfig{ ConfigInfo: cciptypes.ConfigInfo{ - ConfigDigest: expectedDigest, + ConfigDigest: [32]byte{2}, + F: uint8(2), + N: uint8(4), + IsSignatureVerificationEnabled: true, + }, + Signers: [][]byte{{7}, {8}, {9}, {10}}, + Transmitters: [][]byte{{11}, {12}, {13}, {14}}, + }, + }), + createMockBatchResult(&cciptypes.OffRampStaticChainConfig{ + ChainSelector: 200, + GasForCallExactCheck: 10000, + RmnRemote: []byte{15, 16, 17}, + TokenAdminRegistry: []byte{18, 19, 20}, + NonceManager: []byte{21, 22, 23}, + }), + createMockBatchResult(&cciptypes.OffRampDynamicChainConfig{ + FeeQuoter: []byte{24, 25, 26}, + PermissionLessExecutionThresholdSeconds: 3600, + IsRMNVerificationDisabled: false, + MessageInterceptor: []byte{27, 28, 29}, + }), + createMockBatchResult(&cciptypes.SelectorsAndConfigs{ + Selectors: []uint64{1, 2, 3}, + SourceChainConfigs: []cciptypes.SourceChainConfig{ + { + Router: []byte{30, 31, 32}, + IsEnabled: true, + MinSeqNr: 100, + OnRamp: []byte{33, 34, 35}, }, }, }), }, - } - - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) - - digest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeExecute) - require.NoError(t, err) - assert.Equal(t, expectedDigest, digest) - }) -} - -func TestConfigCache_GetRMNVersionedConfig(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - expectedConfig := cciptypes.VersionedConfigRemote{ - Version: 1, - Config: cciptypes.Config{ - RMNHomeContractConfigDigest: [32]byte{1, 2, 3}, - Signers: []cciptypes.Signer{ - { - OnchainPublicKey: []byte{4, 5, 6}, - NodeIndex: 1, - }, + types.BoundContract{Name: consts.ContractNameRMNRemote}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.RMNDigestHeader{ + DigestHeader: [32]byte{3}, + }), + createMockBatchResult(&cciptypes.VersionedConfigRemote{ + Version: 1, + Config: cciptypes.Config{ + RMNHomeContractConfigDigest: [32]byte{4}, + Signers: []cciptypes.Signer{ + { + OnchainPublicKey: []byte{36, 37, 38}, + NodeIndex: 1, + }, + { + OnchainPublicKey: []byte{39, 40, 41}, + NodeIndex: 2, + }, + }, + F: 1, + }, + }), + }, + types.BoundContract{Name: consts.ContractNameRMNProxy}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.Bytes{42, 43, 44}), // remote address + }, + types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.FeeQuoterStaticConfig{ + MaxFeeJuelsPerMsg: cciptypes.BigInt{Int: big.NewInt(1000000)}, + LinkToken: []byte{45, 46, 47}, + StalenessThreshold: 300, + }), }, - F: 2, - }, - } - - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRMNRemote}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.RMNDigestHeader{}), // digest header - createMockBatchResult(&expectedConfig), // versioned config }, + SkippedNoBinds: []string{}, // No skipped contracts } - mockReader.EXPECT().ExtendedBatchGetLatestValues( + // Setup mock to return our test data + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil) + ).Return(expectedResults, nil) - config, err := cache.GetRMNVersionedConfig(tests.Context(t)) + // Force refresh to populate cache + err := cache.refresh(tests.Context(t)) require.NoError(t, err) - assert.Equal(t, expectedConfig, config) -} - -func TestConfigCache_GetOnRampDynamicConfig(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - expectedConfig := cciptypes.GetOnRampDynamicConfigResponse{ - DynamicConfig: cciptypes.OnRampDynamicConfig{ - FeeQuoter: []byte{1, 2, 3}, - AllowListAdmin: []byte{4, 5, 6}, - MessageInterceptor: []byte{7, 8, 9}, - }, - } - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ - createMockBatchResult(&expectedConfig), - }, - } + // Verify Router values + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) + // Verify OnRamp values + onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, []byte{4, 5, 6}, onRampConfig.DynamicConfig.FeeQuoter) + assert.Equal(t, []byte{7, 8, 9}, onRampConfig.DynamicConfig.MessageInterceptor) + assert.Equal(t, []byte{10, 11, 12}, onRampConfig.DynamicConfig.FeeAggregator) + assert.Equal(t, []byte{13, 14, 15}, onRampConfig.DynamicConfig.AllowListAdmin) + assert.False(t, onRampConfig.DynamicConfig.ReentrancyGuardEntered) + + // Verify OffRamp values + commitDigest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeCommit) + require.NoError(t, err) + assert.Equal(t, [32]byte{1}, commitDigest) - config, err := cache.GetOnRampDynamicConfig(tests.Context(t)) + execDigest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeExecute) require.NoError(t, err) - assert.Equal(t, expectedConfig, config) -} + assert.Equal(t, [32]byte{2}, execDigest) -func TestConfigCache_GetOffRampAllChains(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) + staticConfig, err := cache.GetOffRampStaticConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, uint16(10000), staticConfig.GasForCallExactCheck) + assert.Equal(t, []byte{15, 16, 17}, staticConfig.RmnRemote) + assert.Equal(t, []byte{18, 19, 20}, staticConfig.TokenAdminRegistry) + assert.Equal(t, []byte{21, 22, 23}, staticConfig.NonceManager) - expectedConfig := cciptypes.SelectorsAndConfigs{ - Selectors: []uint64{1, 2, 3}, - SourceChainConfigs: []cciptypes.SourceChainConfig{ - { - Router: []byte{1, 2, 3}, - IsEnabled: true, - MinSeqNr: 100, - }, - { - Router: []byte{4, 5, 6}, - IsEnabled: true, - MinSeqNr: 200, - }, - }, - } + dynamicConfig, err := cache.GetOffRampDynamicConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, []byte{24, 25, 26}, dynamicConfig.FeeQuoter) + assert.Equal(t, uint32(3600), dynamicConfig.PermissionLessExecutionThresholdSeconds) + assert.False(t, dynamicConfig.IsRMNVerificationDisabled) + assert.Equal(t, []byte{27, 28, 29}, dynamicConfig.MessageInterceptor) - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.OCRConfigResponse{}), // commit config - createMockBatchResult(&cciptypes.OCRConfigResponse{}), // execute config - createMockBatchResult(&cciptypes.OffRampStaticChainConfig{}), // static config - createMockBatchResult(&cciptypes.OffRampDynamicChainConfig{}), // dynamic config - createMockBatchResult(&expectedConfig), // all chains config - }, - } + allChains, err := cache.GetOffRampAllChains(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, []uint64{1, 2, 3}, allChains.Selectors) + require.Len(t, allChains.SourceChainConfigs, 1) + assert.Equal(t, []byte{30, 31, 32}, allChains.SourceChainConfigs[0].Router) + assert.True(t, allChains.SourceChainConfigs[0].IsEnabled) + assert.Equal(t, uint64(100), allChains.SourceChainConfigs[0].MinSeqNr) + assert.Equal(t, cciptypes.UnknownAddress([]byte{33, 34, 35}), allChains.SourceChainConfigs[0].OnRamp) + + // Verify RMN values + digestHeader, err := cache.GetRMNDigestHeader(tests.Context(t)) + require.NoError(t, err) + expectedDigestHeader := cciptypes.Bytes32{3} // Using Bytes32 type directly + assert.Equal(t, expectedDigestHeader, digestHeader.DigestHeader) - mockReader.EXPECT().ExtendedBatchGetLatestValues( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil) + versionedConfig, err := cache.GetRMNVersionedConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, uint32(1), versionedConfig.Version) + expectedHomeDigest := cciptypes.Bytes32{4} // Using Bytes32 type directly + assert.Equal(t, expectedHomeDigest, versionedConfig.Config.RMNHomeContractConfigDigest) + require.Len(t, versionedConfig.Config.Signers, 2) + assert.Equal(t, []byte{36, 37, 38}, versionedConfig.Config.Signers[0].OnchainPublicKey) + assert.Equal(t, uint64(1), versionedConfig.Config.Signers[0].NodeIndex) + assert.Equal(t, []byte{39, 40, 41}, versionedConfig.Config.Signers[1].OnchainPublicKey) + assert.Equal(t, uint64(2), versionedConfig.Config.Signers[1].NodeIndex) + assert.Equal(t, uint64(1), versionedConfig.Config.F) + + rmnRemote, err := cache.GetRMNRemoteAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{42, 43, 44}, rmnRemote) - config, err := cache.GetOffRampAllChains(tests.Context(t)) + // Verify FeeQuoter values + feeQuoterConfig, err := cache.GetFeeQuoterConfig(tests.Context(t)) require.NoError(t, err) - assert.Equal(t, expectedConfig, config) + assert.Equal(t, big.NewInt(1000000), feeQuoterConfig.MaxFeeJuelsPerMsg.Int) + assert.Equal(t, []byte{45, 46, 47}, feeQuoterConfig.LinkToken) + assert.Equal(t, uint32(300), feeQuoterConfig.StalenessThreshold) } func TestConfigCache_ConcurrentAccess(t *testing.T) { cache, mockReader := setupConfigCacheTest(t) - // Setup mock to always return some data - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + // Setup expected response + mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + }, + types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: []byte{4, 5, 6}, + }, + }), + }, + types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.OCRConfigResponse{ + OCRConfig: cciptypes.OCRConfig{ + ConfigInfo: cciptypes.ConfigInfo{ + ConfigDigest: [32]byte{1}, + }, + }, + }), + }, }, } - mockReader.EXPECT().ExtendedBatchGetLatestValues( + // We expect only one call because of caching + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil).Maybe() + ).Return(mockBatchResults, nil).Once() - // Run multiple goroutines accessing cache simultaneously + // Run multiple goroutines accessing different methods var wg sync.WaitGroup - for i := 0; i < 10; i++ { - wg.Add(1) + numGoroutines := 10 + wg.Add(numGoroutines * 3) + + // Use this channel to coordinate start time + start := make(chan struct{}) + + // Launch goroutines + for i := 0; i < numGoroutines; i++ { + // Test different getter methods concurrently + go func() { + defer wg.Done() + <-start // Wait for start signal + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) + }() + go func() { defer wg.Done() - _, err := cache.GetNativeTokenAddress(tests.Context(t)) + <-start // Wait for start signal + config, err := cache.GetOnRampDynamicConfig(tests.Context(t)) require.NoError(t, err) + assert.Equal(t, []byte{4, 5, 6}, config.DynamicConfig.FeeQuoter) + }() + + go func() { + defer wg.Done() + <-start // Wait for start signal + digest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeCommit) + require.NoError(t, err) + assert.Equal(t, [32]byte{1}, digest) }() } + + // Start all goroutines simultaneously + close(start) + + // Wait for all goroutines to complete wg.Wait() -} -func TestConfigCache_UpdateFromResults(t *testing.T) { - t.Run("handles error getting result", func(t *testing.T) { - cache, _ := setupConfigCacheTest(t) + // Verify the cache worked by checking we only got one mock call + mockReader.AssertExpectations(t) +} - // Create a mock BatchReadResult that returns an error - errResult := &types.BatchReadResult{} - errResult.SetResult(nil, errors.New("failed to get result")) +func TestConfigCache_RefreshInterval(t *testing.T) { + t.Run("respects refresh interval", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) - results := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - *errResult, + // Setup initial call + mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + }, }, } - err := cache.updateFromResults(results) - require.Error(t, err) - assert.Contains(t, err.Error(), "get router result") - }) - - t.Run("handles nil results", func(t *testing.T) { - cache, _ := setupConfigCacheTest(t) + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(mockBatchResults, nil).Once() - results := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(nil), - }, - } + // First call should trigger refresh + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) - err := cache.updateFromResults(results) + // Second immediate call should use cached value without calling mock + addr2, err := cache.GetNativeTokenAddress(tests.Context(t)) require.NoError(t, err) + assert.Equal(t, addr, addr2) }) } -func TestConfigCache_GetFeeQuoterConfig(t *testing.T) { - t.Run("returns cached fee quoter config", func(t *testing.T) { +func TestConfigCache_ErrorHandling(t *testing.T) { + t.Run("handles reader error", func(t *testing.T) { cache, mockReader := setupConfigCacheTest(t) - expectedConfig := cciptypes.FeeQuoterStaticConfig{ - MaxFeeJuelsPerMsg: cciptypes.BigInt{Int: big.NewInt(1000)}, - LinkToken: []byte{1, 2, 3, 4}, - StalenessThreshold: uint32(300), + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(contractreader.BatchGetLatestValuesGracefulResult{}, fmt.Errorf("mock error")).Once() + + _, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.Error(t, err) + assert.Contains(t, err.Error(), "refresh cache") + }) + + t.Run("keeps last valid value when type mismatch occurs", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // First set a valid value + initialResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + }, + }, } - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ - createMockBatchResult(&expectedConfig), + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(initialResults, nil).Once() + + // Initial call to set value + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) + + // Force cache expiry + cache.lastUpdateAt = time.Time{} + + // Now try with invalid type + invalidResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult("wrong type"), // String instead of Bytes + }, }, } - mockReader.EXPECT().ExtendedBatchGetLatestValues( + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil) + ).Return(invalidResults, nil).Once() - config, err := cache.GetFeeQuoterConfig(tests.Context(t)) + // Get value after invalid result - should keep old value + addr, err = cache.GetNativeTokenAddress(tests.Context(t)) require.NoError(t, err) - assert.Equal(t, expectedConfig.MaxFeeJuelsPerMsg, config.MaxFeeJuelsPerMsg) - assert.Equal(t, expectedConfig.LinkToken, config.LinkToken) - assert.Equal(t, expectedConfig.StalenessThreshold, config.StalenessThreshold) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr, "should retain last valid value") }) - t.Run("handles invalid fee quoter config", func(t *testing.T) { + t.Run("propagates type conversion errors", func(t *testing.T) { cache, mockReader := setupConfigCacheTest(t) - invalidConfig := cciptypes.FeeQuoterStaticConfig{ - MaxFeeJuelsPerMsg: cciptypes.BigInt{Int: big.NewInt(0)}, // Invalid zero max fee - LinkToken: []byte{}, // Invalid empty address - StalenessThreshold: uint32(0), // Invalid zero threshold - } + // Create a BatchReadResult that will fail GetResult() + badResult := &types.BatchReadResult{} + badResult.SetResult(nil, fmt.Errorf("conversion error")) - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameFeeQuoter}: []types.BatchReadResult{ - createMockBatchResult(&invalidConfig), + mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + *badResult, + }, }, } - mockReader.EXPECT().ExtendedBatchGetLatestValues( + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil) + ).Return(mockBatchResults, nil).Once() - config, err := cache.GetFeeQuoterConfig(tests.Context(t)) - require.NoError(t, err) // Should not error as validation is not part of the cache - assert.Equal(t, invalidConfig, config) + // Should propagate the error + _, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.Error(t, err) + assert.Contains(t, err.Error(), "conversion error") }) +} - t.Run("handles missing fee quoter config", func(t *testing.T) { +func TestConfigCache_SkippedContractsHandling(t *testing.T) { + t.Run("clears skipped contract values", func(t *testing.T) { cache, mockReader := setupConfigCacheTest(t) - // Return empty results - mockBatchResults := types.BatchGetLatestValuesResult{} + // First response with values + initialResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), + }, + types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: []byte{4, 5, 6}, + }, + }), + }, + }, + } + + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(initialResults, nil).Once() + + // Initial calls to populate cache + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) + + onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, []byte{4, 5, 6}, onRampConfig.DynamicConfig.FeeQuoter) + + // Second response with router skipped + updatedResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{ + types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ + createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ + DynamicConfig: cciptypes.OnRampDynamicConfig{ + FeeQuoter: []byte{7, 8, 9}, + }, + }), + }, + }, + SkippedNoBinds: []string{consts.ContractNameRouter}, + } + + // Force cache expiry + cache.lastUpdateAt = time.Time{} - mockReader.EXPECT().ExtendedBatchGetLatestValues( + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil) + ).Return(updatedResults, nil).Once() + + // Router value should be cleared + addr, err = cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{}, addr, "router value should be cleared") - config, err := cache.GetFeeQuoterConfig(tests.Context(t)) + // OnRamp value should be updated + onRampConfig, err = cache.GetOnRampDynamicConfig(tests.Context(t)) require.NoError(t, err) - assert.Equal(t, cciptypes.FeeQuoterStaticConfig{}, config) // Should return empty config + assert.Equal(t, []byte{7, 8, 9}, onRampConfig.DynamicConfig.FeeQuoter) }) } -func TestConfigCache_GetRMNRemoteAddress(t *testing.T) { - t.Run("returns cached RMN remote address", func(t *testing.T) { +func TestConfigCache_EmptyValues(t *testing.T) { + t.Run("returns zero values for empty results", func(t *testing.T) { cache, mockReader := setupConfigCacheTest(t) - expectedAddress := cciptypes.Bytes{1, 2, 3, 4} - mockBatchResults := types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRMNProxy}: []types.BatchReadResult{ - createMockBatchResult(&expectedAddress), + mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ + Results: types.BatchGetLatestValuesResult{}, + SkippedNoBinds: []string{ + consts.ContractNameRouter, + consts.ContractNameOnRamp, + consts.ContractNameOffRamp, + consts.ContractNameRMNRemote, + consts.ContractNameRMNProxy, + consts.ContractNameFeeQuoter, }, } - mockReader.EXPECT().ExtendedBatchGetLatestValues( + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( mock.Anything, mock.Anything, - ).Return(mockBatchResults, nil) + ).Return(mockBatchResults, nil).Once() + + // Check all getters return zero values + addr, err := cache.GetNativeTokenAddress(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.Bytes{}, addr) + + onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.GetOnRampDynamicConfigResponse{}, onRampConfig) + + offRampConfig, err := cache.GetOffRampStaticConfig(tests.Context(t)) + require.NoError(t, err) + assert.Equal(t, cciptypes.OffRampStaticChainConfig{}, offRampConfig) - address, err := cache.GetRMNRemoteAddress(tests.Context(t)) + feeQuoterConfig, err := cache.GetFeeQuoterConfig(tests.Context(t)) require.NoError(t, err) - assert.Equal(t, expectedAddress, address) + assert.Equal(t, cciptypes.FeeQuoterStaticConfig{}, feeQuoterConfig) + }) +} + +func TestConfigCache_ContextHandling(t *testing.T) { + t.Run("handles cancelled context", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + // Create cancelled context + ctx, cancel := context.WithCancel(tests.Context(t)) + cancel() + + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(contractreader.BatchGetLatestValuesGracefulResult{}, context.Canceled).Once() + + _, err := cache.GetNativeTokenAddress(ctx) + require.Error(t, err) + assert.ErrorIs(t, err, context.Canceled) + }) + + t.Run("handles deadline exceeded", func(t *testing.T) { + cache, mockReader := setupConfigCacheTest(t) + + ctx, cancel := context.WithTimeout(tests.Context(t), time.Nanosecond) + defer cancel() + time.Sleep(time.Microsecond) + + mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( + mock.Anything, + mock.Anything, + ).Return(contractreader.BatchGetLatestValuesGracefulResult{}, context.DeadlineExceeded).Once() + + _, err := cache.GetNativeTokenAddress(ctx) + require.Error(t, err) + assert.ErrorIs(t, err, context.DeadlineExceeded) }) } From ee2e5c21cb376c361c58e3e928b90af49ce56d90 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 10:13:02 +0400 Subject: [PATCH 06/24] fix GetRMNRemoteConfig --- pkg/reader/ccip.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index cb96a1e00..efc9689b6 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -696,11 +696,21 @@ func (r *ccipChainReader) GetRMNRemoteConfig( return rmntypes.RemoteConfig{}, fmt.Errorf("get RMNRemote proxy contract address: %w", err) } + _, err = bindExtendedReaderContract( + ctx, + lggr, + r.contractReaders, + r.destChain, + consts.ContractNameRMNProxy, + proxyContractAddress) + if err != nil { + return rmntypes.RemoteConfig{}, fmt.Errorf("bind RMN proxy contract: %w", err) + } + cache, ok := r.caches[r.destChain] if !ok { return rmntypes.RemoteConfig{}, fmt.Errorf("cache not found for chain %d", r.destChain) } - // rmnRemoteAddress, err := r.getRMNRemoteAddress(ctx, lggr, r.destChain, proxyContractAddress) rmnRemoteAddress, err := cache.GetRMNRemoteAddress(ctx) if err != nil { return rmntypes.RemoteConfig{}, fmt.Errorf("get RMN remote address: %w", err) @@ -716,7 +726,7 @@ func (r *ccipChainReader) GetRMNRemoteConfig( } var header ret - err = r.contractReaders[destChainSelector].ExtendedGetLatestValue( + err = r.contractReaders[r.destChain].ExtendedGetLatestValue( ctx, consts.ContractNameRMNRemote, consts.MethodNameGetReportDigestHeader, From 1348d2e987aada23960c4f6eaa17bf0e5e122534 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 14:37:41 +0400 Subject: [PATCH 07/24] update stucts in types --- pkg/types/ccipocr3/config_types.go | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/pkg/types/ccipocr3/config_types.go b/pkg/types/ccipocr3/config_types.go index 4ab07d7b8..59e62ebc7 100644 --- a/pkg/types/ccipocr3/config_types.go +++ b/pkg/types/ccipocr3/config_types.go @@ -30,15 +30,15 @@ type RMNDigestHeader struct { // //nolint:lll // It's a URL. type FeeQuoterStaticConfig struct { - MaxFeeJuelsPerMsg BigInt - LinkToken []byte - StalenessThreshold uint32 + MaxFeeJuelsPerMsg BigInt `json:"maxFeeJuelsPerMsg"` + LinkToken []byte `json:"linkToken"` + StalenessThreshold uint32 `json:"stalenessThreshold"` } // selectorsAndConfigs wraps the return values from getAllsourceChainConfigs. type SelectorsAndConfigs struct { - Selectors []uint64 - SourceChainConfigs []SourceChainConfig + Selectors []uint64 `mapstructure:"F0"` + SourceChainConfigs []SourceChainConfig `mapstructure:"F1"` } // sourceChainConfig is used to parse the response from the offRamp contract's getSourceChainConfig method. @@ -68,22 +68,30 @@ func (scc SourceChainConfig) Check() (bool /* enabled */, error) { return scc.IsEnabled, nil } +// OffRampStaticChainConfig is used to parse the response from the offRamp contract's getStaticConfig method. +// See: /contracts/src/v0.8/ccip/offRamp/OffRamp.sol:StaticConfig type OffRampStaticChainConfig struct { - ChainSelector ChainSelector - GasForCallExactCheck uint16 - RmnRemote []byte - TokenAdminRegistry []byte - NonceManager []byte + ChainSelector ChainSelector `json:"chainSelector"` + GasForCallExactCheck uint16 `json:"gasForCallExactCheck"` + RmnRemote []byte `json:"rmnRemote"` + TokenAdminRegistry []byte `json:"tokenAdminRegistry"` + NonceManager []byte `json:"nonceManager"` } +// OffRampDynamicChainConfig maps to DynamicConfig in OffRamp.sol type OffRampDynamicChainConfig struct { - FeeQuoter []byte - PermissionLessExecutionThresholdSeconds uint32 - IsRMNVerificationDisabled bool - MessageInterceptor []byte + FeeQuoter []byte `json:"feeQuoter"` + PermissionLessExecutionThresholdSeconds uint32 `json:"permissionLessExecutionThresholdSeconds"` + IsRMNVerificationDisabled bool `json:"isRMNVerificationDisabled"` + MessageInterceptor []byte `json:"messageInterceptor"` } + +// We're wrapping the onRampDynamicConfig this way to map to on-chain return type which is a named struct +// https://github.com/smartcontractkit/chainlink/blob/12af1de88238e0e918177d6b5622070417f48adf/contracts/src/v0.8/ccip/onRamp/OnRamp.sol#L328 +// +//nolint:lll type GetOnRampDynamicConfigResponse struct { - DynamicConfig OnRampDynamicConfig + DynamicConfig OnRampDynamicConfig `json:"dynamicConfig"` } // See DynamicChainConfig in OnRamp.sol @@ -98,11 +106,11 @@ type OnRampDynamicConfig struct { // VersionedConfigRemote is used to parse the response from the RMNRemote contract's getVersionedConfig method. // See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L167-L169 type VersionedConfigRemote struct { - Version uint32 - Config Config + Version uint32 `json:"version"` + Config Config `json:"config"` } -// config is used to parse the response from the RMNRemote contract's getVersionedConfig method. +// Config is used to parse the response from the RMNRemote contract's getVersionedConfig method. // See: https://github.com/smartcontractkit/ccip/blob/ccip-develop/contracts/src/v0.8/ccip/rmn/RMNRemote.sol#L49-L53 type Config struct { RMNHomeContractConfigDigest Bytes32 `json:"rmnHomeContractConfigDigest"` From fba20e2c2d8947d3ec048ad918bbda06745e8b3f Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 15:01:56 +0400 Subject: [PATCH 08/24] add debug log on offrampallchains --- pkg/reader/ccip.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index efc9689b6..3ef4a864a 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -849,6 +849,8 @@ func (r *ccipChainReader) discoverOffRampContracts( return nil, fmt.Errorf("unable to get SourceChainsConfig: %w", err) } + lggr.Debugw("got source chain configs", "configs", selectorsAndConfigs) + // Iterate results in sourceChain selector order so that the router config is deterministic. for i := range selectorsAndConfigs.Selectors { sourceChain := cciptypes.ChainSelector(selectorsAndConfigs.Selectors[i]) From f601a4aaccb7ad2cc8261570d28f652cf41ac9aa Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 15:28:24 +0400 Subject: [PATCH 09/24] more logs --- pkg/reader/configcache/config_cache.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 3060cdcb1..4e7942e46 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -75,6 +75,7 @@ func NewConfigCache(reader contractreader.Extended, lggr logger.Logger) ConfigCa // refreshIfNeeded refreshes the cache if the refresh interval has elapsed func (c *configCache) refreshIfNeeded(ctx context.Context) error { + c.lggr.Infow("In refreshIfNeeded") c.cacheMu.Lock() defer c.cacheMu.Unlock() @@ -82,6 +83,8 @@ func (c *configCache) refreshIfNeeded(ctx context.Context) error { return nil } + c.lggr.Debugw("Refreshing cache") + if err := c.refresh(ctx); err != nil { return fmt.Errorf("refresh cache: %w", err) } @@ -106,6 +109,8 @@ func (c *configCache) refresh(ctx context.Context) error { c.clearSkippedContractValues(batchResult.SkippedNoBinds) } + c.lggr.Infow("batchResult is", "batchResult", batchResult) + if err := c.updateFromResults(batchResult.Results); err != nil { return fmt.Errorf("update cache from results: %w", err) } @@ -420,9 +425,12 @@ func (c *configCache) GetOffRampDynamicConfig(ctx context.Context) (cciptypes.Of // GetOffRampAllChains returns the cached offramp all chains config func (c *configCache) GetOffRampAllChains(ctx context.Context) (cciptypes.SelectorsAndConfigs, error) { + c.lggr.Infow("In GetOffRampAllChains") if err := c.refreshIfNeeded(ctx); err != nil { return cciptypes.SelectorsAndConfigs{}, fmt.Errorf("refresh cache: %w", err) } + c.lggr.Infow("After refreshIfNeeded") + c.lggr.Infow("The offrampAllChains is", "offrampAllChains", c.offrampAllChains) c.cacheMu.RLock() defer c.cacheMu.RUnlock() From aaddac58a785f79d2e509e5729863a32e7780d3d Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 16:09:00 +0400 Subject: [PATCH 10/24] more logs --- pkg/reader/configcache/config_cache.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 4e7942e46..d969fa2f8 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -101,7 +101,9 @@ func (c *configCache) refresh(ctx context.Context) error { if err != nil { return fmt.Errorf("batch get configs: %w", err) } - + c.lggr.Infow("batchResult result len is", "batchResult", len(batchResult.Results)) + c.lggr.Infow("batchResult is", "batchResult", batchResult.Results) + c.lggr.Infow("batchResult.SkippedNoBinds is", "batchResult.SkippedNoBinds", batchResult.SkippedNoBinds) // Log skipped contracts if any for debugging // Clear skipped contract values from cache if len(batchResult.SkippedNoBinds) > 0 { @@ -109,8 +111,6 @@ func (c *configCache) refresh(ctx context.Context) error { c.clearSkippedContractValues(batchResult.SkippedNoBinds) } - c.lggr.Infow("batchResult is", "batchResult", batchResult) - if err := c.updateFromResults(batchResult.Results); err != nil { return fmt.Errorf("update cache from results: %w", err) } From 4bc7cef065e6101ba1e97b0908ff2cf2549ef110 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 16:29:11 +0400 Subject: [PATCH 11/24] results --- pkg/reader/configcache/config_cache.go | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index d969fa2f8..30f41d885 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -101,8 +101,27 @@ func (c *configCache) refresh(ctx context.Context) error { if err != nil { return fmt.Errorf("batch get configs: %w", err) } - c.lggr.Infow("batchResult result len is", "batchResult", len(batchResult.Results)) - c.lggr.Infow("batchResult is", "batchResult", batchResult.Results) + // type BatchGetLatestValuesResult map[BoundContract]ContractBatchResults + // type ContractBatchResults []BatchReadResult + // type BatchReadResult struct { + // ReadName string + // returnValue any + // err error + // } + + // print the batchResult + for contract, results := range batchResult.Results { + c.lggr.Infow("contract is", "contract", contract) + for i, result := range results { + c.lggr.Infow("result is", "result", result) + val, err := result.GetResult() + if err != nil { + c.lggr.Errorw("get offramp result %d: %w", i, err) + } + c.lggr.Infow("val is", "val", val) + } + } + c.lggr.Infow("batchResult.SkippedNoBinds is", "batchResult.SkippedNoBinds", batchResult.SkippedNoBinds) // Log skipped contracts if any for debugging // Clear skipped contract values from cache From 6a55cb6724a1bf8b57272b0c2570fd51685e1aaa Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 16:43:32 +0400 Subject: [PATCH 12/24] more logs^2 --- pkg/reader/configcache/config_cache.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 30f41d885..c92fd01d0 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -101,14 +101,6 @@ func (c *configCache) refresh(ctx context.Context) error { if err != nil { return fmt.Errorf("batch get configs: %w", err) } - // type BatchGetLatestValuesResult map[BoundContract]ContractBatchResults - // type ContractBatchResults []BatchReadResult - // type BatchReadResult struct { - // ReadName string - // returnValue any - // err error - // } - // print the batchResult for contract, results := range batchResult.Results { c.lggr.Infow("contract is", "contract", contract) @@ -238,6 +230,7 @@ func (c *configCache) handleContractResults(contract types.BoundContract, result case consts.ContractNameOnRamp: return c.handleOnRampResults(results) case consts.ContractNameOffRamp: + c.lggr.Infow("In handleContractResults") return c.handleOffRampResults(results) case consts.ContractNameRMNRemote: return c.handleRMNRemoteResults(results) @@ -284,25 +277,31 @@ func (c *configCache) handleOffRampResults(results []types.BatchReadResult) erro if err != nil { return fmt.Errorf("get offramp result %d: %w", i, err) } + c.lggr.Infow("val is", "val", val) switch i { case 0: if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.lggr.Infow("typed is", "typed", typed) c.commitLatestOCRConfig = *typed } case 1: if typed, ok := val.(*cciptypes.OCRConfigResponse); ok { + c.lggr.Infow("typed is", "typed", typed) c.execLatestOCRConfig = *typed } case 2: if typed, ok := val.(*cciptypes.OffRampStaticChainConfig); ok { + c.lggr.Infow("typed is", "typed", typed) c.offrampStaticConfig = *typed } case 3: if typed, ok := val.(*cciptypes.OffRampDynamicChainConfig); ok { + c.lggr.Infow("typed is", "typed", typed) c.offrampDynamicConfig = *typed } case 4: if typed, ok := val.(*cciptypes.SelectorsAndConfigs); ok { + c.lggr.Infow("typed is", "typed", typed) c.offrampAllChains = *typed } } From 38404a87489cc53a38090c1e893e3f0da9540e8d Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 17:05:56 +0400 Subject: [PATCH 13/24] rm clearing --- pkg/reader/configcache/config_cache.go | 49 ++--- pkg/reader/configcache/config_cache_test.go | 214 ++++++++++---------- 2 files changed, 132 insertions(+), 131 deletions(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index c92fd01d0..c086bbd0d 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -80,6 +80,7 @@ func (c *configCache) refreshIfNeeded(ctx context.Context) error { defer c.cacheMu.Unlock() if time.Since(c.lastUpdateAt) < configCacheRefreshInterval { + c.lggr.Infow("Not refreshing cache") return nil } @@ -119,7 +120,7 @@ func (c *configCache) refresh(ctx context.Context) error { // Clear skipped contract values from cache if len(batchResult.SkippedNoBinds) > 0 { c.lggr.Infow("some contracts were skipped due to no bindings: %v", batchResult.SkippedNoBinds) - c.clearSkippedContractValues(batchResult.SkippedNoBinds) + // c.clearSkippedContractValues(batchResult.SkippedNoBinds) } if err := c.updateFromResults(batchResult.Results); err != nil { @@ -359,29 +360,29 @@ func (c *configCache) handleFeeQuoterResults(results []types.BatchReadResult) er } // clearSkippedContractValues resets cache values for contracts that had no bindings -func (c *configCache) clearSkippedContractValues(skippedContracts []string) { - for _, contractName := range skippedContracts { - switch contractName { - case consts.ContractNameRouter: - c.nativeTokenAddress = cciptypes.Bytes{} - case consts.ContractNameOnRamp: - c.onrampDynamicConfig = cciptypes.GetOnRampDynamicConfigResponse{} - case consts.ContractNameOffRamp: - c.commitLatestOCRConfig = cciptypes.OCRConfigResponse{} - c.execLatestOCRConfig = cciptypes.OCRConfigResponse{} - c.offrampStaticConfig = cciptypes.OffRampStaticChainConfig{} - c.offrampDynamicConfig = cciptypes.OffRampDynamicChainConfig{} - c.offrampAllChains = cciptypes.SelectorsAndConfigs{} - case consts.ContractNameRMNRemote: - c.rmnDigestHeader = cciptypes.RMNDigestHeader{} - c.rmnVersionedConfig = cciptypes.VersionedConfigRemote{} - case consts.ContractNameRMNProxy: - c.rmnRemoteAddress = cciptypes.Bytes{} - case consts.ContractNameFeeQuoter: - c.feeQuoterConfig = cciptypes.FeeQuoterStaticConfig{} - } - } -} +// func (c *configCache) clearSkippedContractValues(skippedContracts []string) { +// for _, contractName := range skippedContracts { +// switch contractName { +// case consts.ContractNameRouter: +// c.nativeTokenAddress = cciptypes.Bytes{} +// case consts.ContractNameOnRamp: +// c.onrampDynamicConfig = cciptypes.GetOnRampDynamicConfigResponse{} +// case consts.ContractNameOffRamp: +// c.commitLatestOCRConfig = cciptypes.OCRConfigResponse{} +// c.execLatestOCRConfig = cciptypes.OCRConfigResponse{} +// c.offrampStaticConfig = cciptypes.OffRampStaticChainConfig{} +// c.offrampDynamicConfig = cciptypes.OffRampDynamicChainConfig{} +// c.offrampAllChains = cciptypes.SelectorsAndConfigs{} +// case consts.ContractNameRMNRemote: +// c.rmnDigestHeader = cciptypes.RMNDigestHeader{} +// c.rmnVersionedConfig = cciptypes.VersionedConfigRemote{} +// case consts.ContractNameRMNProxy: +// c.rmnRemoteAddress = cciptypes.Bytes{} +// case consts.ContractNameFeeQuoter: +// c.feeQuoterConfig = cciptypes.FeeQuoterStaticConfig{} +// } +// } +// } func (c *configCache) GetOffRampConfigDigest(ctx context.Context, pluginType uint8) ([32]byte, error) { if err := c.refreshIfNeeded(ctx); err != nil { diff --git a/pkg/reader/configcache/config_cache_test.go b/pkg/reader/configcache/config_cache_test.go index d8929a7d3..6d7536222 100644 --- a/pkg/reader/configcache/config_cache_test.go +++ b/pkg/reader/configcache/config_cache_test.go @@ -428,113 +428,113 @@ func TestConfigCache_ErrorHandling(t *testing.T) { }) } -func TestConfigCache_SkippedContractsHandling(t *testing.T) { - t.Run("clears skipped contract values", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - // First response with values - initialResults := contractreader.BatchGetLatestValuesGracefulResult{ - Results: types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), - }, - types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ - DynamicConfig: cciptypes.OnRampDynamicConfig{ - FeeQuoter: []byte{4, 5, 6}, - }, - }), - }, - }, - } - - mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( - mock.Anything, - mock.Anything, - ).Return(initialResults, nil).Once() - - // Initial calls to populate cache - addr, err := cache.GetNativeTokenAddress(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) - - onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, []byte{4, 5, 6}, onRampConfig.DynamicConfig.FeeQuoter) - - // Second response with router skipped - updatedResults := contractreader.BatchGetLatestValuesGracefulResult{ - Results: types.BatchGetLatestValuesResult{ - types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ - DynamicConfig: cciptypes.OnRampDynamicConfig{ - FeeQuoter: []byte{7, 8, 9}, - }, - }), - }, - }, - SkippedNoBinds: []string{consts.ContractNameRouter}, - } - - // Force cache expiry - cache.lastUpdateAt = time.Time{} - - mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( - mock.Anything, - mock.Anything, - ).Return(updatedResults, nil).Once() - - // Router value should be cleared - addr, err = cache.GetNativeTokenAddress(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.Bytes{}, addr, "router value should be cleared") - - // OnRamp value should be updated - onRampConfig, err = cache.GetOnRampDynamicConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, []byte{7, 8, 9}, onRampConfig.DynamicConfig.FeeQuoter) - }) -} - -func TestConfigCache_EmptyValues(t *testing.T) { - t.Run("returns zero values for empty results", func(t *testing.T) { - cache, mockReader := setupConfigCacheTest(t) - - mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ - Results: types.BatchGetLatestValuesResult{}, - SkippedNoBinds: []string{ - consts.ContractNameRouter, - consts.ContractNameOnRamp, - consts.ContractNameOffRamp, - consts.ContractNameRMNRemote, - consts.ContractNameRMNProxy, - consts.ContractNameFeeQuoter, - }, - } - - mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( - mock.Anything, - mock.Anything, - ).Return(mockBatchResults, nil).Once() - - // Check all getters return zero values - addr, err := cache.GetNativeTokenAddress(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.Bytes{}, addr) - - onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.GetOnRampDynamicConfigResponse{}, onRampConfig) - - offRampConfig, err := cache.GetOffRampStaticConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.OffRampStaticChainConfig{}, offRampConfig) - - feeQuoterConfig, err := cache.GetFeeQuoterConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, cciptypes.FeeQuoterStaticConfig{}, feeQuoterConfig) - }) -} +// func TestConfigCache_SkippedContractsHandling(t *testing.T) { +// t.Run("clears skipped contract values", func(t *testing.T) { +// cache, mockReader := setupConfigCacheTest(t) + +// // First response with values +// initialResults := contractreader.BatchGetLatestValuesGracefulResult{ +// Results: types.BatchGetLatestValuesResult{ +// types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ +// createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), +// }, +// types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ +// createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ +// DynamicConfig: cciptypes.OnRampDynamicConfig{ +// FeeQuoter: []byte{4, 5, 6}, +// }, +// }), +// }, +// }, +// } + +// mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( +// mock.Anything, +// mock.Anything, +// ).Return(initialResults, nil).Once() + +// // Initial calls to populate cache +// addr, err := cache.GetNativeTokenAddress(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) + +// onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, []byte{4, 5, 6}, onRampConfig.DynamicConfig.FeeQuoter) + +// // Second response with router skipped +// updatedResults := contractreader.BatchGetLatestValuesGracefulResult{ +// Results: types.BatchGetLatestValuesResult{ +// types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ +// createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ +// DynamicConfig: cciptypes.OnRampDynamicConfig{ +// FeeQuoter: []byte{7, 8, 9}, +// }, +// }), +// }, +// }, +// SkippedNoBinds: []string{consts.ContractNameRouter}, +// } + +// // Force cache expiry +// cache.lastUpdateAt = time.Time{} + +// mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( +// mock.Anything, +// mock.Anything, +// ).Return(updatedResults, nil).Once() + +// // Router value should be cleared +// addr, err = cache.GetNativeTokenAddress(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.Bytes{}, addr, "router value should be cleared") + +// // OnRamp value should be updated +// onRampConfig, err = cache.GetOnRampDynamicConfig(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, []byte{7, 8, 9}, onRampConfig.DynamicConfig.FeeQuoter) +// }) +// } + +// func TestConfigCache_EmptyValues(t *testing.T) { +// t.Run("returns zero values for empty results", func(t *testing.T) { +// cache, mockReader := setupConfigCacheTest(t) + +// mockBatchResults := contractreader.BatchGetLatestValuesGracefulResult{ +// Results: types.BatchGetLatestValuesResult{}, +// SkippedNoBinds: []string{ +// consts.ContractNameRouter, +// consts.ContractNameOnRamp, +// consts.ContractNameOffRamp, +// consts.ContractNameRMNRemote, +// consts.ContractNameRMNProxy, +// consts.ContractNameFeeQuoter, +// }, +// } + +// mockReader.EXPECT().ExtendedBatchGetLatestValuesGraceful( +// mock.Anything, +// mock.Anything, +// ).Return(mockBatchResults, nil).Once() + +// // Check all getters return zero values +// addr, err := cache.GetNativeTokenAddress(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.Bytes{}, addr) + +// onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.GetOnRampDynamicConfigResponse{}, onRampConfig) + +// offRampConfig, err := cache.GetOffRampStaticConfig(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.OffRampStaticChainConfig{}, offRampConfig) + +// feeQuoterConfig, err := cache.GetFeeQuoterConfig(tests.Context(t)) +// require.NoError(t, err) +// assert.Equal(t, cciptypes.FeeQuoterStaticConfig{}, feeQuoterConfig) +// }) +// } func TestConfigCache_ContextHandling(t *testing.T) { t.Run("handles cancelled context", func(t *testing.T) { From b809996681484b82eb3203b25df0a0e1869568ff Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 17:08:15 +0400 Subject: [PATCH 14/24] update logs --- pkg/reader/configcache/config_cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index c086bbd0d..3cd3f1f34 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -119,7 +119,7 @@ func (c *configCache) refresh(ctx context.Context) error { // Log skipped contracts if any for debugging // Clear skipped contract values from cache if len(batchResult.SkippedNoBinds) > 0 { - c.lggr.Infow("some contracts were skipped due to no bindings: %v", batchResult.SkippedNoBinds) + c.lggr.Infow("some contracts were skipped due to no bindings", "contracts", batchResult.SkippedNoBinds) // c.clearSkippedContractValues(batchResult.SkippedNoBinds) } From f1c2ccbadca66c605168d8bf725ea9208ab2a423 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 18:09:38 +0400 Subject: [PATCH 15/24] log cach refreshed --- pkg/reader/configcache/config_cache.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 3cd3f1f34..dc75f6344 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -91,6 +91,7 @@ func (c *configCache) refreshIfNeeded(ctx context.Context) error { } c.lastUpdateAt = time.Now() + c.lggr.Infow("Cache refreshed", "lastUpdateAt", c.lastUpdateAt) return nil } From 798c649a33a7e5b74396e6a8478efdcbbe95f728 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 18:40:03 +0400 Subject: [PATCH 16/24] configCacheRefreshInterval 0 --- pkg/reader/configcache/config_cache.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index dc75f6344..94e5f46df 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -16,7 +16,7 @@ import ( ) const ( - configCacheRefreshInterval = 30 * time.Second + configCacheRefreshInterval = 0 * time.Second ) // configCacher defines the interface for accessing cached config values From 73b4789f5f09ad9ac99f2889a7f2b69e5567f240 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 19:33:55 +0400 Subject: [PATCH 17/24] more logs --- internal/plugincommon/discovery/processor.go | 4 ++++ pkg/reader/ccip.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/internal/plugincommon/discovery/processor.go b/internal/plugincommon/discovery/processor.go index b9b057424..50b686f63 100644 --- a/internal/plugincommon/discovery/processor.go +++ b/internal/plugincommon/discovery/processor.go @@ -74,6 +74,8 @@ func (cdp *ContractDiscoveryProcessor) Observation( return dt.Observation{}, fmt.Errorf("unable to discover contracts: %w, seqNr: %d", err, seqNr) } + cdp.lggr.Infow("Discovered contracts", "contracts", contracts, "seqNr", seqNr) + return dt.Observation{ FChain: fChain, Addresses: contracts, @@ -319,6 +321,8 @@ func (cdp *ContractDiscoveryProcessor) Outcome( } contracts[consts.ContractNameRouter] = routerConsensus + cdp.lggr.Infow("Contracts to be synced", "contracts", contracts) + // call Sync to bind contracts. if err := (*cdp.reader).Sync(ctx, contracts); err != nil { return dt.Outcome{}, fmt.Errorf("unable to sync contracts: %w", err) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 3ef4a864a..b6b7b0d05 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -907,6 +907,8 @@ func (r *ccipChainReader) DiscoverContracts(ctx context.Context) (ContractAddres myChains := maps.Keys(r.contractReaders) + r.lggr.Infow("discovering contracts", "chains", myChains, "destChain", r.destChain, "resp", resp) + // Read onRamps for FeeQuoter in DynamicConfig. dynamicConfigs := r.getOnRampDynamicConfigs(ctx, lggr, myChains) for chain, cfg := range dynamicConfigs { From bee00384f3f9913dc581b07399e717d636d7c292 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 20:54:19 +0400 Subject: [PATCH 18/24] more logs --- pkg/reader/ccip.go | 60 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index b6b7b0d05..2017cb187 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -849,7 +849,7 @@ func (r *ccipChainReader) discoverOffRampContracts( return nil, fmt.Errorf("unable to get SourceChainsConfig: %w", err) } - lggr.Debugw("got source chain configs", "configs", selectorsAndConfigs) + lggr.Infow("got source chain configs", "configs", selectorsAndConfigs) // Iterate results in sourceChain selector order so that the router config is deterministic. for i := range selectorsAndConfigs.Selectors { @@ -888,6 +888,64 @@ func (r *ccipChainReader) discoverOffRampContracts( return resp, nil } +func (r *ccipChainReader) getOnRampDynamicConfigs( + ctx context.Context, + lggr logger.Logger, + srcChains []cciptypes.ChainSelector, +) map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse { + result := make(map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse) + + mu := new(sync.Mutex) + wg := new(sync.WaitGroup) + for _, chainSel := range srcChains { + // no onramp for the destination chain + if chainSel == r.destChain { + continue + } + if r.contractReaders[chainSel] == nil { + r.lggr.Errorw("contract reader not found", "chain", chainSel) + continue + } + + wg.Add(1) + go func(chainSel cciptypes.ChainSelector) { + defer wg.Done() + // read onramp dynamic config + resp := cciptypes.GetOnRampDynamicConfigResponse{} + err := r.contractReaders[chainSel].ExtendedGetLatestValue( + ctx, + consts.ContractNameOnRamp, + consts.MethodNameOnRampGetDynamicConfig, + primitives.Unconfirmed, + map[string]any{}, + &resp, + ) + lggr.Debugw("got onramp dynamic config", + "chain", chainSel, + "resp", resp) + if err != nil { + if errors.Is(err, contractreader.ErrNoBindings) { + // ErrNoBindings is an allowable error during initialization + lggr.Infow( + "unable to lookup source fee quoters (onRamp dynamic config), "+ + "this is expected during initialization", "err", err) + } else { + lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", + "chain", chainSel, "err", err) + } + return + } + mu.Lock() + result[chainSel] = resp + mu.Unlock() + }(chainSel) + } + + wg.Wait() + + return result +} + func (r *ccipChainReader) DiscoverContracts(ctx context.Context) (ContractAddresses, error) { lggr := logutil.WithContextValues(ctx, r.lggr) resp := make(ContractAddresses) From 93d75460c8e52d4868bacf04334c9b9017963929 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 21:07:12 +0400 Subject: [PATCH 19/24] fix GetOffRampAllChains --- pkg/reader/ccip.go | 42 +++++++++++++++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 2017cb187..42666e041 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "math/big" + "sort" "strconv" "sync" "time" @@ -851,15 +852,42 @@ func (r *ccipChainReader) discoverOffRampContracts( lggr.Infow("got source chain configs", "configs", selectorsAndConfigs) - // Iterate results in sourceChain selector order so that the router config is deterministic. + configs := make(map[cciptypes.ChainSelector]cciptypes.SourceChainConfig) + + if len(selectorsAndConfigs.SourceChainConfigs) != len(selectorsAndConfigs.Selectors) { + return nil, fmt.Errorf("selectors and source chain configs length mismatch: %v", configs) + } + + lggr.Debugw("got source chain configs", "configs", configs) + + // Populate the map. for i := range selectorsAndConfigs.Selectors { - sourceChain := cciptypes.ChainSelector(selectorsAndConfigs.Selectors[i]) + chainSel := cciptypes.ChainSelector(selectorsAndConfigs.Selectors[i]) cfg := selectorsAndConfigs.SourceChainConfigs[i] - resp = resp.Append(consts.ContractNameOnRamp, sourceChain, cfg.OnRamp) - // The local router is located in each source sourceChain config. Add it once. - if len(resp[consts.ContractNameRouter][chain]) == 0 { - resp = resp.Append(consts.ContractNameRouter, chain, cfg.Router) - lggr.Infow("appending router contract address", "address", cfg.Router) + + enabled, err := cfg.Check() + if err != nil { + return nil, fmt.Errorf("source chain config check for chain %d failed: %w", chainSel, err) + } + if !enabled { + // We don't want to process disabled chains prematurely. + lggr.Debugw("source chain is disabled", "chain", chainSel) + continue + } + + configs[chainSel] = cfg + + // Iterate results in sourceChain selector order so that the router config is deterministic. + keys := maps.Keys(configs) + sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] }) + for _, sourceChain := range keys { + cfg := configs[sourceChain] + resp = resp.Append(consts.ContractNameOnRamp, sourceChain, cfg.OnRamp) + // The local router is located in each source sourceChain config. Add it once. + if len(resp[consts.ContractNameRouter][chain]) == 0 { + resp = resp.Append(consts.ContractNameRouter, chain, cfg.Router) + lggr.Infow("appending router contract address", "address", cfg.Router) + } } } } From cedf8a7b6e54105999d52884d5f35097e370c6c9 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 21:08:23 +0400 Subject: [PATCH 20/24] logs --- pkg/reader/ccip.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 42666e041..911c90b63 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -883,6 +883,7 @@ func (r *ccipChainReader) discoverOffRampContracts( for _, sourceChain := range keys { cfg := configs[sourceChain] resp = resp.Append(consts.ContractNameOnRamp, sourceChain, cfg.OnRamp) + lggr.Infow("appending onramp contract address", "address", cfg.OnRamp) // The local router is located in each source sourceChain config. Add it once. if len(resp[consts.ContractNameRouter][chain]) == 0 { resp = resp.Append(consts.ContractNameRouter, chain, cfg.Router) From 45192a0391c37b5642f14da0808af50b2afe87b0 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 21:18:36 +0400 Subject: [PATCH 21/24] rm duplicate --- pkg/reader/ccip.go | 58 ---------------------------------------------- 1 file changed, 58 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 911c90b63..a72d0cb7c 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -917,64 +917,6 @@ func (r *ccipChainReader) discoverOffRampContracts( return resp, nil } -func (r *ccipChainReader) getOnRampDynamicConfigs( - ctx context.Context, - lggr logger.Logger, - srcChains []cciptypes.ChainSelector, -) map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse { - result := make(map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse) - - mu := new(sync.Mutex) - wg := new(sync.WaitGroup) - for _, chainSel := range srcChains { - // no onramp for the destination chain - if chainSel == r.destChain { - continue - } - if r.contractReaders[chainSel] == nil { - r.lggr.Errorw("contract reader not found", "chain", chainSel) - continue - } - - wg.Add(1) - go func(chainSel cciptypes.ChainSelector) { - defer wg.Done() - // read onramp dynamic config - resp := cciptypes.GetOnRampDynamicConfigResponse{} - err := r.contractReaders[chainSel].ExtendedGetLatestValue( - ctx, - consts.ContractNameOnRamp, - consts.MethodNameOnRampGetDynamicConfig, - primitives.Unconfirmed, - map[string]any{}, - &resp, - ) - lggr.Debugw("got onramp dynamic config", - "chain", chainSel, - "resp", resp) - if err != nil { - if errors.Is(err, contractreader.ErrNoBindings) { - // ErrNoBindings is an allowable error during initialization - lggr.Infow( - "unable to lookup source fee quoters (onRamp dynamic config), "+ - "this is expected during initialization", "err", err) - } else { - lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", - "chain", chainSel, "err", err) - } - return - } - mu.Lock() - result[chainSel] = resp - mu.Unlock() - }(chainSel) - } - - wg.Wait() - - return result -} - func (r *ccipChainReader) DiscoverContracts(ctx context.Context) (ContractAddresses, error) { lggr := logutil.WithContextValues(ctx, r.lggr) resp := make(ContractAddresses) From 3de1b8585e2f8ccbb8fb4f36c3a59ec01e3e0491 Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Fri, 31 Jan 2025 21:53:03 +0400 Subject: [PATCH 22/24] fix GetOnRampDynamicConfig by reverting --- pkg/reader/ccip.go | 58 +++++++++++++-------- pkg/reader/configcache/config_cache.go | 37 ------------- pkg/reader/configcache/config_cache_test.go | 28 ---------- 3 files changed, 37 insertions(+), 86 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index a72d0cb7c..5653534dd 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -858,8 +858,6 @@ func (r *ccipChainReader) discoverOffRampContracts( return nil, fmt.Errorf("selectors and source chain configs length mismatch: %v", configs) } - lggr.Debugw("got source chain configs", "configs", configs) - // Populate the map. for i := range selectorsAndConfigs.Selectors { chainSel := cciptypes.ChainSelector(selectorsAndConfigs.Selectors[i]) @@ -1171,36 +1169,54 @@ func (r *ccipChainReader) getOnRampDynamicConfigs( ) map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse { result := make(map[cciptypes.ChainSelector]cciptypes.GetOnRampDynamicConfigResponse) + mu := new(sync.Mutex) + wg := new(sync.WaitGroup) for _, chainSel := range srcChains { // no onramp for the destination chain if chainSel == r.destChain { continue } - - cache, ok := r.caches[chainSel] - if !ok { - lggr.Errorw("cache not found for chain selector", "chain", chainSel) + if r.contractReaders[chainSel] == nil { + r.lggr.Errorw("contract reader not found", "chain", chainSel) continue } - resp, err := cache.GetOnRampDynamicConfig(ctx) - if err != nil { - if errors.Is(err, contractreader.ErrNoBindings) { - // ErrNoBindings is an allowable error during initialization - lggr.Infow( - "unable to lookup source fee quoters (onRamp dynamic config), "+ - "this is expected during initialization", "err", err) - } else { - lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", - "chain", chainSel, "err", err) + wg.Add(1) + go func(chainSel cciptypes.ChainSelector) { + defer wg.Done() + // read onramp dynamic config + resp := cciptypes.GetOnRampDynamicConfigResponse{} + err := r.contractReaders[chainSel].ExtendedGetLatestValue( + ctx, + consts.ContractNameOnRamp, + consts.MethodNameOnRampGetDynamicConfig, + primitives.Unconfirmed, + map[string]any{}, + &resp, + ) + lggr.Debugw("got onramp dynamic config", + "chain", chainSel, + "resp", resp) + if err != nil { + if errors.Is(err, contractreader.ErrNoBindings) { + // ErrNoBindings is an allowable error during initialization + lggr.Infow( + "unable to lookup source fee quoters (onRamp dynamic config), "+ + "this is expected during initialization", "err", err) + } else { + lggr.Errorw("unable to lookup source fee quoters (onRamp dynamic config)", + "chain", chainSel, "err", err) + } + return } - continue - } - - lggr.Debugw("got onramp dynamic config", "chain", chainSel, "resp", resp) - result[chainSel] = resp + mu.Lock() + result[chainSel] = resp + mu.Unlock() + }(chainSel) } + wg.Wait() + return result } diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 94e5f46df..13a6f3370 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -27,9 +27,6 @@ type ConfigCacher interface { // Token related methods GetNativeTokenAddress(ctx context.Context) (cciptypes.Bytes, error) - // OnRamp related methods - GetOnRampDynamicConfig(ctx context.Context) (cciptypes.GetOnRampDynamicConfigResponse, error) - // OffRamp related methods GetOffRampStaticConfig(ctx context.Context) (cciptypes.OffRampStaticChainConfig, error) GetOffRampDynamicConfig(ctx context.Context) (cciptypes.OffRampDynamicChainConfig, error) @@ -58,7 +55,6 @@ type configCache struct { offrampStaticConfig cciptypes.OffRampStaticChainConfig offrampDynamicConfig cciptypes.OffRampDynamicChainConfig offrampAllChains cciptypes.SelectorsAndConfigs - onrampDynamicConfig cciptypes.GetOnRampDynamicConfigResponse rmnDigestHeader cciptypes.RMNDigestHeader rmnVersionedConfig cciptypes.VersionedConfigRemote rmnRemoteAddress cciptypes.Bytes @@ -135,7 +131,6 @@ func (c *configCache) refresh(ctx context.Context) error { func (c *configCache) prepareBatchRequests() contractreader.ExtendedBatchGetLatestValuesRequest { var ( nativeTokenAddress cciptypes.Bytes - onrampDynamicConfig cciptypes.GetOnRampDynamicConfigResponse commitLatestOCRConfig cciptypes.OCRConfigResponse execLatestOCRConfig cciptypes.OCRConfigResponse staticConfig cciptypes.OffRampStaticChainConfig @@ -153,11 +148,6 @@ func (c *configCache) prepareBatchRequests() contractreader.ExtendedBatchGetLate Params: map[string]any{}, ReturnVal: &nativeTokenAddress, }}, - consts.ContractNameOnRamp: {{ - ReadName: consts.MethodNameOnRampGetDynamicConfig, - Params: map[string]any{}, - ReturnVal: &onrampDynamicConfig, - }}, consts.ContractNameOffRamp: { { ReadName: consts.MethodNameOffRampLatestConfigDetails, @@ -229,8 +219,6 @@ func (c *configCache) handleContractResults(contract types.BoundContract, result switch contract.Name { case consts.ContractNameRouter: return c.handleRouterResults(results) - case consts.ContractNameOnRamp: - return c.handleOnRampResults(results) case consts.ContractNameOffRamp: c.lggr.Infow("In handleContractResults") return c.handleOffRampResults(results) @@ -258,20 +246,6 @@ func (c *configCache) handleRouterResults(results []types.BatchReadResult) error return nil } -// handleOnRampResults processes onramp-specific results -func (c *configCache) handleOnRampResults(results []types.BatchReadResult) error { - if len(results) > 0 { - val, err := results[0].GetResult() - if err != nil { - return fmt.Errorf("get onramp result: %w", err) - } - if typed, ok := val.(*cciptypes.GetOnRampDynamicConfigResponse); ok { - c.onrampDynamicConfig = *typed - } - } - return nil -} - // handleOffRampResults processes offramp-specific results func (c *configCache) handleOffRampResults(results []types.BatchReadResult) error { for i, result := range results { @@ -410,17 +384,6 @@ func (c *configCache) GetNativeTokenAddress(ctx context.Context) (cciptypes.Byte return c.nativeTokenAddress, nil } -// GetOnRampDynamicConfig returns the cached onramp dynamic config -func (c *configCache) GetOnRampDynamicConfig(ctx context.Context) (cciptypes.GetOnRampDynamicConfigResponse, error) { - if err := c.refreshIfNeeded(ctx); err != nil { - return cciptypes.GetOnRampDynamicConfigResponse{}, fmt.Errorf("refresh cache: %w", err) - } - - c.cacheMu.RLock() - defer c.cacheMu.RUnlock() - return c.onrampDynamicConfig, nil -} - // GetOffRampStaticConfig returns the cached offramp static config func (c *configCache) GetOffRampStaticConfig(ctx context.Context) (cciptypes.OffRampStaticChainConfig, error) { if err := c.refreshIfNeeded(ctx); err != nil { diff --git a/pkg/reader/configcache/config_cache_test.go b/pkg/reader/configcache/config_cache_test.go index 6d7536222..223e9e744 100644 --- a/pkg/reader/configcache/config_cache_test.go +++ b/pkg/reader/configcache/config_cache_test.go @@ -49,17 +49,6 @@ func TestConfigCache_Refresh_BasicScenario(t *testing.T) { types.BoundContract{Name: consts.ContractNameRouter}: []types.BatchReadResult{ createMockBatchResult(&cciptypes.Bytes{1, 2, 3}), // native token address }, - types.BoundContract{Name: consts.ContractNameOnRamp}: []types.BatchReadResult{ - createMockBatchResult(&cciptypes.GetOnRampDynamicConfigResponse{ - DynamicConfig: cciptypes.OnRampDynamicConfig{ - FeeQuoter: []byte{4, 5, 6}, - ReentrancyGuardEntered: false, - MessageInterceptor: []byte{7, 8, 9}, - FeeAggregator: []byte{10, 11, 12}, - AllowListAdmin: []byte{13, 14, 15}, - }, - }), - }, types.BoundContract{Name: consts.ContractNameOffRamp}: []types.BatchReadResult{ createMockBatchResult(&cciptypes.OCRConfigResponse{ // commit config OCRConfig: cciptypes.OCRConfig{ @@ -161,15 +150,6 @@ func TestConfigCache_Refresh_BasicScenario(t *testing.T) { require.NoError(t, err) assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) - // Verify OnRamp values - onRampConfig, err := cache.GetOnRampDynamicConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, []byte{4, 5, 6}, onRampConfig.DynamicConfig.FeeQuoter) - assert.Equal(t, []byte{7, 8, 9}, onRampConfig.DynamicConfig.MessageInterceptor) - assert.Equal(t, []byte{10, 11, 12}, onRampConfig.DynamicConfig.FeeAggregator) - assert.Equal(t, []byte{13, 14, 15}, onRampConfig.DynamicConfig.AllowListAdmin) - assert.False(t, onRampConfig.DynamicConfig.ReentrancyGuardEntered) - // Verify OffRamp values commitDigest, err := cache.GetOffRampConfigDigest(tests.Context(t), consts.PluginTypeCommit) require.NoError(t, err) @@ -285,14 +265,6 @@ func TestConfigCache_ConcurrentAccess(t *testing.T) { assert.Equal(t, cciptypes.Bytes{1, 2, 3}, addr) }() - go func() { - defer wg.Done() - <-start // Wait for start signal - config, err := cache.GetOnRampDynamicConfig(tests.Context(t)) - require.NoError(t, err) - assert.Equal(t, []byte{4, 5, 6}, config.DynamicConfig.FeeQuoter) - }() - go func() { defer wg.Done() <-start // Wait for start signal From 2734f07a7ca800c1f1b13a84f7a9d19ff16f401e Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Sun, 2 Feb 2025 14:57:19 +0400 Subject: [PATCH 23/24] get onramp dynamic config --- pkg/reader/ccip.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/reader/ccip.go b/pkg/reader/ccip.go index 5653534dd..7640f4a2d 100644 --- a/pkg/reader/ccip.go +++ b/pkg/reader/ccip.go @@ -1194,9 +1194,8 @@ func (r *ccipChainReader) getOnRampDynamicConfigs( map[string]any{}, &resp, ) - lggr.Debugw("got onramp dynamic config", - "chain", chainSel, - "resp", resp) + lggr.Infow("got onramp dynamic config", + "chain", chainSel) if err != nil { if errors.Is(err, contractreader.ErrNoBindings) { // ErrNoBindings is an allowable error during initialization From 538c64f555a528694208739372fb27894021e76b Mon Sep 17 00:00:00 2001 From: nogo <0xnogo@gmail.com> Date: Mon, 3 Feb 2025 13:44:57 +0400 Subject: [PATCH 24/24] update type to []byte --- mocks/pkg/reader/configcache/config_cacher.go | 70 ++----------------- pkg/reader/configcache/config_cache.go | 12 ++-- 2 files changed, 14 insertions(+), 68 deletions(-) diff --git a/mocks/pkg/reader/configcache/config_cacher.go b/mocks/pkg/reader/configcache/config_cacher.go index 36ebe84b0..d0bfa96d2 100644 --- a/mocks/pkg/reader/configcache/config_cacher.go +++ b/mocks/pkg/reader/configcache/config_cacher.go @@ -364,62 +364,6 @@ func (_c *MockConfigCacher_GetOffRampStaticConfig_Call) RunAndReturn(run func(co return _c } -// GetOnRampDynamicConfig provides a mock function with given fields: ctx -func (_m *MockConfigCacher) GetOnRampDynamicConfig(ctx context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error) { - ret := _m.Called(ctx) - - if len(ret) == 0 { - panic("no return value specified for GetOnRampDynamicConfig") - } - - var r0 ccipocr3.GetOnRampDynamicConfigResponse - var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error)); ok { - return rf(ctx) - } - if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.GetOnRampDynamicConfigResponse); ok { - r0 = rf(ctx) - } else { - r0 = ret.Get(0).(ccipocr3.GetOnRampDynamicConfigResponse) - } - - if rf, ok := ret.Get(1).(func(context.Context) error); ok { - r1 = rf(ctx) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// MockConfigCacher_GetOnRampDynamicConfig_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOnRampDynamicConfig' -type MockConfigCacher_GetOnRampDynamicConfig_Call struct { - *mock.Call -} - -// GetOnRampDynamicConfig is a helper method to define mock.On call -// - ctx context.Context -func (_e *MockConfigCacher_Expecter) GetOnRampDynamicConfig(ctx interface{}) *MockConfigCacher_GetOnRampDynamicConfig_Call { - return &MockConfigCacher_GetOnRampDynamicConfig_Call{Call: _e.mock.On("GetOnRampDynamicConfig", ctx)} -} - -func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) Run(run func(ctx context.Context)) *MockConfigCacher_GetOnRampDynamicConfig_Call { - _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context)) - }) - return _c -} - -func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) Return(_a0 ccipocr3.GetOnRampDynamicConfigResponse, _a1 error) *MockConfigCacher_GetOnRampDynamicConfig_Call { - _c.Call.Return(_a0, _a1) - return _c -} - -func (_c *MockConfigCacher_GetOnRampDynamicConfig_Call) RunAndReturn(run func(context.Context) (ccipocr3.GetOnRampDynamicConfigResponse, error)) *MockConfigCacher_GetOnRampDynamicConfig_Call { - _c.Call.Return(run) - return _c -} - // GetRMNDigestHeader provides a mock function with given fields: ctx func (_m *MockConfigCacher) GetRMNDigestHeader(ctx context.Context) (ccipocr3.RMNDigestHeader, error) { ret := _m.Called(ctx) @@ -477,23 +421,23 @@ func (_c *MockConfigCacher_GetRMNDigestHeader_Call) RunAndReturn(run func(contex } // GetRMNRemoteAddress provides a mock function with given fields: ctx -func (_m *MockConfigCacher) GetRMNRemoteAddress(ctx context.Context) (ccipocr3.Bytes, error) { +func (_m *MockConfigCacher) GetRMNRemoteAddress(ctx context.Context) ([]byte, error) { ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for GetRMNRemoteAddress") } - var r0 ccipocr3.Bytes + var r0 []byte var r1 error - if rf, ok := ret.Get(0).(func(context.Context) (ccipocr3.Bytes, error)); ok { + if rf, ok := ret.Get(0).(func(context.Context) ([]byte, error)); ok { return rf(ctx) } - if rf, ok := ret.Get(0).(func(context.Context) ccipocr3.Bytes); ok { + if rf, ok := ret.Get(0).(func(context.Context) []byte); ok { r0 = rf(ctx) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(ccipocr3.Bytes) + r0 = ret.Get(0).([]byte) } } @@ -524,12 +468,12 @@ func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) Run(run func(ctx context.Co return _c } -func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) Return(_a0 ccipocr3.Bytes, _a1 error) *MockConfigCacher_GetRMNRemoteAddress_Call { +func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) Return(_a0 []byte, _a1 error) *MockConfigCacher_GetRMNRemoteAddress_Call { _c.Call.Return(_a0, _a1) return _c } -func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) RunAndReturn(run func(context.Context) (ccipocr3.Bytes, error)) *MockConfigCacher_GetRMNRemoteAddress_Call { +func (_c *MockConfigCacher_GetRMNRemoteAddress_Call) RunAndReturn(run func(context.Context) ([]byte, error)) *MockConfigCacher_GetRMNRemoteAddress_Call { _c.Call.Return(run) return _c } diff --git a/pkg/reader/configcache/config_cache.go b/pkg/reader/configcache/config_cache.go index 13a6f3370..47aff81b2 100644 --- a/pkg/reader/configcache/config_cache.go +++ b/pkg/reader/configcache/config_cache.go @@ -35,7 +35,7 @@ type ConfigCacher interface { // RMN related methods GetRMNDigestHeader(ctx context.Context) (cciptypes.RMNDigestHeader, error) GetRMNVersionedConfig(ctx context.Context) (cciptypes.VersionedConfigRemote, error) - GetRMNRemoteAddress(ctx context.Context) (cciptypes.Bytes, error) + GetRMNRemoteAddress(ctx context.Context) ([]byte, error) // FeeQuoter related methods GetFeeQuoterConfig(ctx context.Context) (cciptypes.FeeQuoterStaticConfig, error) @@ -57,7 +57,7 @@ type configCache struct { offrampAllChains cciptypes.SelectorsAndConfigs rmnDigestHeader cciptypes.RMNDigestHeader rmnVersionedConfig cciptypes.VersionedConfigRemote - rmnRemoteAddress cciptypes.Bytes + rmnRemoteAddress []byte feeQuoterConfig cciptypes.FeeQuoterStaticConfig } @@ -313,8 +313,10 @@ func (c *configCache) handleRMNProxyResults(results []types.BatchReadResult) err if err != nil { return fmt.Errorf("get rmn proxy result: %w", err) } - if typed, ok := val.(*cciptypes.Bytes); ok { + if typed, ok := val.(*[]byte); ok { c.rmnRemoteAddress = *typed + } else { + c.lggr.Infow("handleRMNProxyResults - typed is", "typed", typed) } } return nil @@ -454,9 +456,9 @@ func (c *configCache) GetFeeQuoterConfig(ctx context.Context) (cciptypes.FeeQuot } // GetRMNRemoteAddress returns the cached RMN remote address -func (c *configCache) GetRMNRemoteAddress(ctx context.Context) (cciptypes.Bytes, error) { +func (c *configCache) GetRMNRemoteAddress(ctx context.Context) ([]byte, error) { if err := c.refreshIfNeeded(ctx); err != nil { - return cciptypes.Bytes{}, fmt.Errorf("refresh cache: %w", err) + return nil, fmt.Errorf("refresh cache: %w", err) } c.cacheMu.RLock()