11package adapters
22
33import (
4+ "context"
45 "fmt"
6+ "sync"
57
68 "github.com/Masterminds/semver/v3"
9+ "github.com/ethereum/go-ethereum/accounts/abi/bind"
710 "github.com/ethereum/go-ethereum/common"
8- "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_2_0/router "
11+ "github.com/smartcontractkit/chainlink-common/pkg/logger "
912 cldf_chain "github.com/smartcontractkit/chainlink-deployments-framework/chain"
1013 "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
1114 "github.com/smartcontractkit/chainlink-deployments-framework/datastore"
1215 cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
1316 cldf_ops "github.com/smartcontractkit/chainlink-deployments-framework/operations"
17+ "golang.org/x/sync/errgroup"
1418
19+ adapters1_2 "github.com/smartcontractkit/chainlink-ccip/chains/evm/deployment/v1_2_0/adapters"
1520 "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_5_0/evm_2_evm_offramp"
1621 "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_5_0/evm_2_evm_onramp"
1722 "github.com/smartcontractkit/chainlink-ccip/chains/evm/gobindings/generated/v1_5_0/token_admin_registry"
@@ -29,14 +34,25 @@ import (
2934 "github.com/smartcontractkit/chainlink-ccip/deployment/utils/sequences"
3035)
3136
32- var GetTokensPaginationSize = uint64 (20 )
37+ var (
38+ getTokensPaginationSize = uint64 (20 )
39+ // getSupportedTokensPoolConcurrency caps concurrent RPC calls when fetching supported tokens per pool.
40+ // Limits in-flight requests to avoid overwhelming the node/provider (rate limits, timeouts) and memory.
41+ getSupportedTokensPoolConcurrency = 10
42+ )
3343
3444type ConfigImportAdapter struct {
3545 OnRamp map [uint64 ]common.Address
3646 OffRamp map [uint64 ]common.Address
3747 TokenAdminReg common.Address
3848 PriceRegistry common.Address
3949 Router common.Address
50+
51+ // connectedChainsCache memoizes the result of ConnectedChains per chain selector
52+ // to avoid duplicate (potentially expensive) RPC work when the method is called
53+ // multiple times for the same chain within the same adapter instance.
54+ connectedChainsCache map [uint64 ][]uint64
55+ connectedChainsMu sync.Mutex
4056}
4157
4258func (ci * ConfigImportAdapter ) InitializeAdapter (e cldf.Environment , sel uint64 ) error {
@@ -110,28 +126,42 @@ func (ci *ConfigImportAdapter) InitializeAdapter(e cldf.Environment, sel uint64)
110126}
111127
112128func (ci * ConfigImportAdapter ) ConnectedChains (e cldf.Environment , chainsel uint64 ) ([]uint64 , error ) {
113- var connected []uint64
114- // to ensure deduplication in case there are multiple onramps addresses in datastore for the same remote chain selector
115- var mapConnectedChains = make (map [uint64 ]bool )
116- chain , ok := e .BlockChains .EVMChains ()[chainsel ]
117- if ! ok {
118- return nil , fmt .Errorf ("chain with selector %d not found in environment" , chainsel )
129+ // Fast path: return cached result if available to avoid duplicate RPC work.
130+ ci .connectedChainsMu .Lock ()
131+ if ci .connectedChainsCache == nil {
132+ ci .connectedChainsCache = make (map [uint64 ][]uint64 )
133+ }
134+ if cached , ok := ci .connectedChainsCache [chainsel ]; ok {
135+ // Return a copy to prevent callers from mutating the cached slice.
136+ result := make ([]uint64 , len (cached ))
137+ copy (result , cached )
138+ ci .connectedChainsMu .Unlock ()
139+ return result , nil
119140 }
120- routerC , err := router .NewRouter (ci .Router , chain .Client )
141+ ci .connectedChainsMu .Unlock ()
142+
143+ var connected []uint64
144+ laneResolver := adapters1_2.LaneVersionResolver {}
145+ remoteChainToVersionMap , _ , err := laneResolver .DeriveLaneVersionsForChain (e , chainsel )
121146 if err != nil {
122- return nil , fmt .Errorf ("failed to instantiate router contract at %s on chain %d: %w" , ci . Router . String (), chain . Selector , err )
147+ return nil , fmt .Errorf ("failed to derive lane versions for chain %d: %w" , chainsel , err )
123148 }
124- for destSel , onrampForDest := range ci .OnRamp {
125- onRamp , err := routerC .GetOnRamp (nil , destSel )
126- if err != nil {
127- return nil , fmt .Errorf ("failed to get onramp for dest chain %d from router at %s on chain %d: %w" , destSel , ci .Router .String (), chain .Selector , err )
128- }
129- // if the onramp address from the router doesn't match the onramp address we have, then this chain is not actually connected with 1.5
130- if onRamp == onrampForDest && ! mapConnectedChains [destSel ] {
149+ for destSel , version := range remoteChainToVersionMap {
150+ if version .Equal (semver .MustParse ("1.5.0" )) {
131151 connected = append (connected , destSel )
132- mapConnectedChains [destSel ] = true
133152 }
134153 }
154+
155+ // Cache the computed result for subsequent calls.
156+ ci .connectedChainsMu .Lock ()
157+ if ci .connectedChainsCache == nil {
158+ ci .connectedChainsCache = make (map [uint64 ][]uint64 )
159+ }
160+ cached := make ([]uint64 , len (connected ))
161+ copy (cached , connected )
162+ ci .connectedChainsCache [chainsel ] = cached
163+ ci .connectedChainsMu .Unlock ()
164+
135165 return connected , nil
136166}
137167
@@ -140,8 +170,12 @@ func (ci *ConfigImportAdapter) SupportedTokensPerRemoteChain(e cldf.Environment,
140170 if ! ok {
141171 return nil , fmt .Errorf ("chain with selector %d not found in environment" , chainsel )
142172 }
173+ remoteChains , err := ci .ConnectedChains (e , chainsel )
174+ if err != nil {
175+ return nil , fmt .Errorf ("failed to get connected chains for chain %d: %w" , chainsel , err )
176+ }
143177 // get all supported tokens from token admin registry
144- return GetSupportedTokensPerRemoteChain (ci .TokenAdminReg , chain )
178+ return GetSupportedTokensPerRemoteChain (e . GetContext (), e . Logger , ci .TokenAdminReg , chain , remoteChains )
145179}
146180
147181func (ci * ConfigImportAdapter ) SequenceImportConfig () * cldf_ops.Sequence [api.ImportConfigPerChainInput , sequences.OnChainOutput , cldf_chain.BlockChains ] {
@@ -181,7 +215,7 @@ func (ci *ConfigImportAdapter) SequenceImportConfig() *cldf_ops.Sequence[api.Imp
181215 })
182216}
183217
184- func GetSupportedTokensPerRemoteChain (tokenAdminRegAddr common.Address , chain evm.Chain ) (map [uint64 ][]common.Address , error ) {
218+ func GetSupportedTokensPerRemoteChain (ctx context. Context , l logger. Logger , tokenAdminRegAddr common.Address , chain evm.Chain , remoteChains [] uint64 ) (map [uint64 ][]common.Address , error ) {
185219 // get all supported tokens from token admin registry
186220 tokenAdminRegC , err := token_admin_registry .NewTokenAdminRegistry (tokenAdminRegAddr , chain .Client )
187221 if err != nil {
@@ -190,13 +224,13 @@ func GetSupportedTokensPerRemoteChain(tokenAdminRegAddr common.Address, chain ev
190224 startIndex := uint64 (0 )
191225 allTokens := make ([]common.Address , 0 )
192226 for {
193- fetchedTokens , err := tokenAdminRegC .GetAllConfiguredTokens (nil , startIndex , GetTokensPaginationSize )
227+ fetchedTokens , err := tokenAdminRegC .GetAllConfiguredTokens (nil , startIndex , getTokensPaginationSize )
194228 if err != nil {
195229 return nil , err
196230 }
197231 allTokens = append (allTokens , fetchedTokens ... )
198- startIndex += GetTokensPaginationSize
199- if uint64 (len (fetchedTokens )) < GetTokensPaginationSize {
232+ startIndex += getTokensPaginationSize
233+ if uint64 (len (fetchedTokens )) < getTokensPaginationSize {
200234 break
201235 }
202236 }
@@ -205,26 +239,80 @@ func GetSupportedTokensPerRemoteChain(tokenAdminRegAddr common.Address, chain ev
205239 return nil , fmt .Errorf ("failed to get pools for tokens from token admin registry at %s on chain %d: %w" , tokenAdminRegAddr .String (), chain .Selector , err )
206240 }
207241 tokensPerRemoteChain := make (map [uint64 ][]common.Address )
242+ var mu sync.Mutex
243+ grp , grpCtx := errgroup .WithContext (ctx )
244+ grp .SetLimit (getSupportedTokensPoolConcurrency )
208245 for _ , poolAddr := range pools {
209246 // there is no supported pool for this token
210247 if poolAddr == (common.Address {}) {
211248 continue
212249 }
213- tokenPoolC , err := token_pool .NewTokenPool (poolAddr , chain .Client )
214- if err != nil {
215- return nil , fmt .Errorf ("failed to instantiate token pool contract at %s on chain %d: %w" , poolAddr .String (), chain .Selector , err )
216- }
217- chains , err := tokenPoolC .GetSupportedChains (nil )
218- if err != nil {
219- return nil , fmt .Errorf ("failed to get supported chains from token pool at %s on chain %d: %w" , poolAddr .String (), chain .Selector , err )
220- }
221- tokenAddr , err := tokenPoolC .GetToken (nil )
222- if err != nil {
223- return nil , fmt .Errorf ("failed to get token address from token pool at %s on chain %d: %w" , poolAddr .String (), chain .Selector , err )
224- }
225- for _ , remoteChain := range chains {
226- tokensPerRemoteChain [remoteChain ] = append (tokensPerRemoteChain [remoteChain ], tokenAddr )
227- }
250+ poolAddr := poolAddr
251+ grp .Go (func () error {
252+ tokenPoolC , err := token_pool .NewTokenPool (poolAddr , chain .Client )
253+ if err != nil {
254+ return fmt .Errorf ("failed to instantiate token pool contract at %s on chain %d: %w" , poolAddr .String (), chain .Selector , err )
255+ }
256+
257+ // Cache the token address per pool so we only fetch it once, and
258+ // track when certain pool methods appear to be unsupported so we
259+ // can avoid repeated failed calls and warning spam.
260+ var (
261+ tokenAddr common.Address
262+ tokenFetched bool
263+ isSupportedChainUnsupported bool
264+ getTokenUnsupported bool
265+ )
266+
267+ for _ , remoteChain := range remoteChains {
268+ // If we've already determined that IsSupportedChain or GetToken
269+ // are unsupported for this pool, stop checking further chains.
270+ if isSupportedChainUnsupported || getTokenUnsupported {
271+ break
272+ }
273+
274+ supported , err := tokenPoolC .IsSupportedChain (& bind.CallOpts {
275+ Context : grpCtx ,
276+ }, remoteChain )
277+ if err != nil {
278+ // If we fail to check if the pool supports a remote chain,
279+ // assume this method isn't supported by this pool, log once,
280+ // and short-circuit to avoid failing the entire import and
281+ // spamming warnings for every remote chain.
282+ l .Warnf ("failed to check if token pool at %s on chain %d supports remote chain %d: %v" , poolAddr .String (), chain .Selector , remoteChain , err )
283+ isSupportedChainUnsupported = true
284+ break
285+ }
286+ if ! supported {
287+ continue
288+ }
289+
290+ // Fetch the token address at most once per pool.
291+ if ! tokenFetched {
292+ tokenAddr , err = tokenPoolC .GetToken (& bind.CallOpts {
293+ Context : grpCtx ,
294+ })
295+ if err != nil {
296+ // If we fail to get the token address for a pool, assume
297+ // this method isn't supported or is consistently failing
298+ // for this pool. Log once and short-circuit further
299+ // attempts for this pool to avoid warning spam.
300+ l .Warnf ("failed to get token address for token pool at %s on chain %d: %v" , poolAddr .String (), chain .Selector , err )
301+ getTokenUnsupported = true
302+ break
303+ }
304+ tokenFetched = true
305+ }
306+
307+ mu .Lock ()
308+ tokensPerRemoteChain [remoteChain ] = append (tokensPerRemoteChain [remoteChain ], tokenAddr )
309+ mu .Unlock ()
310+ }
311+ return nil
312+ })
313+ }
314+ if err := grp .Wait (); err != nil {
315+ return nil , err
228316 }
229317 return tokensPerRemoteChain , nil
230318}
0 commit comments