Skip to content

Conversation

@graham-chainlink
Copy link
Collaborator

Chains are loaded lazily instead of eager, this means chains will only be loaded when it is being used, this means users are not forced to provide any secrets for chains if they are not used upfront and avoid loading huge amount of chains that are never used.

This change also means we can deprecate ChainOverrides feature as we no longer have to tell CLD what chains to load beforehand.

@changeset-bot
Copy link

changeset-bot bot commented Dec 29, 2025

🦋 Changeset detected

Latest commit: b71011b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
chainlink-deployments-framework Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

// default - loads all chains from the networks config
chainSelectorsToLoad := cfg.Networks.ChainSelectors()

if loadcfg.chainSelectorsToLoad != nil {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer care about what chains to load via chainSelectorsToLoad as we no longer load everything.

@graham-chainlink graham-chainlink changed the title feat: introducing lazy chain loading [Concept]: feat: introducing lazy chain loading Dec 29, 2025
@graham-chainlink graham-chainlink force-pushed the ggoh/lazy-blockchain branch 3 times, most recently from 2c1b033 to a5255df Compare December 29, 2025 04:46
@cl-sonarqube-production
Copy link

// Unlike EVMChains, this method returns an error if any chain fails to load.
// The error may contain multiple chain load failures wrapped together.
// Successfully loaded chains are still returned in the map.
func (l *LazyBlockChains) TryEVMChains() (map[uint64]evm.Chain, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use a generic implementation here and reduce a lot of the duplication in this file:

func tryChains[T any, PT *T](l *LazyBlockChains, family string) (map[uint64]T, error) {
	l.mu.RLock()
	selectors := make([]uint64, 0)
	for selector, f := range l.availableChains {
		if f == family {
			selectors = append(selectors, selector)
		}
	}
	l.mu.RUnlock()

	chains := make(map[uint64]T)
	var errs []error

	for _, selector := range selectors {
		chain, err := l.GetBySelector(selector)
		if err != nil {
			errs = append(errs, fmt.Errorf("failed to load chain %d: %w", selector, err))

			continue
		}
		switch c := chain.(type) {
		case T:
			chains[selector] = c
		case PT:
			if c != nil {
				chains[selector] = *c
			}
		}
	}

	if len(errs) > 0 {
		return chains, errors.Join(errs...)
	}

	return chains, nil
}

// then

func (l *LazyBlockChains) EVMChains() map[uint64]evm.Chain {
	chains, err := tryChains[evm.Chain](l, chainsel.FamilyEVM)
	if err != nil {
		l.lggr.Errorw("Failed to load one or more EVM chains", "error", err)
	}

	return chains
}

func (l *LazyBlockChains) SolanaChains() map[uint64]solana.Chain {
	chains, err := tryChains[solana.Chain](l, chainsel.FamilySolana)
	if err != nil {
		l.lggr.Errorw("Failed to load one or more Solana chains", "error", err)
	}

	return chains
}

// etc...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Good pick up, using generic cleaned it up alot, thanks! I had to use the type constraint for it to work

func tryChains[T any, PT interface {
	*T
}](l *LazyBlockChains, family string) (map[uint64]T, error) {

chains := make(map[uint64]evm.Chain)
var errs []error

for _, selector := range selectors {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this must be done in parallel for the lazy loading to become a viable option.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done! another good pick up, updated the other getters to be parallel too!

)

// ChainLoader is an interface for loading a blockchain instance lazily.
type ChainLoader interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you try to do

type ChainLoader = fchain.ChainLoader

in engine/cld/chains/chains.go?

I think it could simplify a few interfaces and avoid a few map/slice copies.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah i knew about this but decided to keep both interface in the separate package, there were no copying as it is just the interface instead of different struct. But type alias looks cleaner, i updated to use it!

blockChains, err := chains.LoadChains(ctx, lggr, cfg, chainSelectorsToLoad)
// Use lazy loading for chains - they will be initialized on first access
// All chains from the network config are made available, but only loaded when accessed
lazyBlockChains, err := chains.NewLazyBlockChains(ctx, lggr, cfg)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should feature flag this initially. Nothing too complicated, just check for an environment variable -- say, CLD_LAZY_BLOCK_CHAINS=1.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added a feature toggle.

Chains are loaded lazily instead of eager, this means chains will only be loaded when it is being used, this means users are not forced to provide any secrets for chains if they are not used upfront and avoid loading huge amount of chains that are never used.

This change also means we can deprecate `ChainOverrides` feature as we no longer have to tell CLD what chains to load.

This lazy loading is hidden under the feature flag CLD_LAZY_BLOCKCHAINS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants