Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor ccip launcher to mirror on chain state(CCIP-2687) #15065

Merged
merged 16 commits into from
Nov 4, 2024
Merged
148 changes: 28 additions & 120 deletions core/capabilities/ccip/launcher/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,140 +3,48 @@
import (
"fmt"

cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"

ocrtypes "github.com/smartcontractkit/libocr/offchainreporting2plus/types"
"go.uber.org/multierr"

ccipreaderpkg "github.com/smartcontractkit/chainlink-ccip/pkg/reader"
cctypes "github.com/smartcontractkit/chainlink/v2/core/capabilities/ccip/types"
)

// activeCandidateDeployment represents a active-candidate deployment of OCR instances.
type activeCandidateDeployment struct {
// active is the active OCR instance.
// active must always be present.
active cctypes.CCIPOracle

// candidate is the candidate OCR instance.
// candidate may or may not be present.
// candidate must never be present if active is not present.
candidate cctypes.CCIPOracle
}

// ccipDeployment represents active-candidate deployments of both commit and exec
// OCR instances.
type ccipDeployment struct {
commit activeCandidateDeployment
exec activeCandidateDeployment
}

// Close shuts down all OCR instances in the deployment.
func (c *ccipDeployment) Close() error {
// we potentially run into this situation when
// trying to close an active instance that doesn't exist
// this check protects us from nil pointer exception

if c == nil {
return nil
}
var err error

// shutdown active commit instance.
if c.commit.active != nil {
err = multierr.Append(err, c.commit.active.Close())
}

// shutdown candidate commit instance.
if c.commit.candidate != nil {
err = multierr.Append(err, c.commit.candidate.Close())
}

// shutdown active exec instance.
if c.exec.active != nil {
err = multierr.Append(err, c.exec.active.Close())
}

// shutdown candidate exec instance.
if c.exec.candidate != nil {
err = multierr.Append(err, c.exec.candidate.Close())
}
type ccipPlugins map[ocrtypes.ConfigDigest]cctypes.CCIPOracle
0xAustinWang marked this conversation as resolved.
Show resolved Hide resolved

return err
// StartAll will call Oracle.Start on an entire don
func (c ccipPlugins) StartAll() error {
nilPlugins := make(ccipPlugins)
0xAustinWang marked this conversation as resolved.
Show resolved Hide resolved
return c.Transition(nilPlugins)
}

// StartActive starts the active OCR instances.
func (c *ccipDeployment) StartActive() error {
var err error

err = multierr.Append(err, c.commit.active.Start())
err = multierr.Append(err, c.exec.active.Start())

return err
// CloseAll is used to shut down an entire don immediately
func (c ccipPlugins) CloseAll() error {
nilPlugins := make(ccipPlugins)
return nilPlugins.Transition(c)
0xAustinWang marked this conversation as resolved.
Show resolved Hide resolved
}

// CloseActive shuts down the active OCR instances.
func (c *ccipDeployment) CloseActive() error {
// Transition manages starting and stopping ocr instances
// If there are any new config digests, we need to start those instances
// If any of the previous config digests are no longer present, we need to shut those down
// We don't care about if they're exec/commit or active/candidate, that all happens in the plugin
func (c ccipPlugins) Transition(prevPlugins ccipPlugins) error {
var err error

err = multierr.Append(err, c.commit.active.Close())
err = multierr.Append(err, c.exec.active.Close())

return err
}

// TransitionDeployment handles the active-candidate deployment transition.
// prevDeployment is the previous deployment state.
// there are two possible cases:
//
// 1. both active and candidate are present in prevDeployment, but only active is present in c.
// this is a promotion of candidate to active, so we need to shut down the active deployment
// and make candidate the new active. In this case candidate is already running, so there's no
// need to start it. However, we need to shut down the active deployment.
//
// 2. only active is present in prevDeployment, both active and candidate are present in c.
// In this case, active is already running, so there's no need to start it. We need to
// start candidate.
func (c *ccipDeployment) TransitionDeployment(prevDeployment *ccipDeployment) error {
if prevDeployment == nil {
return fmt.Errorf("previous deployment is nil")
if len(c) > 4 || len(prevPlugins) > 4 {
0xAustinWang marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("have more than 4 instances somehow")

Check failure on line 34 in core/capabilities/ccip/launcher/deployment.go

View workflow job for this annotation

GitHub Actions / lint

fmt.Errorf can be replaced with errors.New (perfsprint)
0xAustinWang marked this conversation as resolved.
Show resolved Hide resolved
}

var err error
if prevDeployment.commit.candidate != nil && c.commit.candidate == nil {
err = multierr.Append(err, prevDeployment.commit.active.Close())
} else if prevDeployment.commit.candidate == nil && c.commit.candidate != nil {
err = multierr.Append(err, c.commit.candidate.Start())
} else {
return fmt.Errorf("invalid active-candidate deployment transition")
// This shuts down instances that were present previously, but are no longer needed
for digest, oracle := range prevPlugins {
if _, ok := c[digest]; !ok {
err = multierr.Append(err, oracle.Close())
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I believe we could close them in parallel to get some performance boost.

Also maybe it's easier to use sets i.e. mapset for figuring out which oracles needs to be closed and which ones are new.

old = someSet()
new = someSet()

close = old - new 
open = new - old

Copy link
Contributor

@makramkd makramkd Nov 4, 2024

Choose a reason for hiding this comment

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

Ha interesting thing is that this set sub logic is mirrored in the diff code. Maybe needs a lib to unify 🤔

Copy link
Contributor

@jmank88 jmank88 Nov 4, 2024

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated to start and close in parallel.

In this situation since we have a max of 4 oracles only, i think using a mapset is unnecessary. We also key the oracles based on config digest, which would be less meaningful in a mapset.

}
}

if prevDeployment.exec.candidate != nil && c.exec.candidate == nil {
err = multierr.Append(err, prevDeployment.exec.active.Close())
} else if prevDeployment.exec.candidate == nil && c.exec.candidate != nil {
err = multierr.Append(err, c.exec.candidate.Start())
} else {
return fmt.Errorf("invalid active-candidate deployment transition")
// This will start the instances that were not previously present, but are in the new config
for digest, oracle := range c {
if _, ok := prevPlugins[digest]; !ok {
err = multierr.Append(err, oracle.Start())
}
}

return err
}

// HasCandidateInstance returns true if the deployment has a candidate instance for the
// given plugin type.
func (c *ccipDeployment) HasCandidateInstance(pluginType cctypes.PluginType) bool {
switch pluginType {
case cctypes.PluginTypeCCIPCommit:
return c.commit.candidate != nil
case cctypes.PluginTypeCCIPExec:
return c.exec.candidate != nil
default:
return false
}
}

func isNewCandidateInstance(pluginType cctypes.PluginType, ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta, prevDeployment ccipDeployment) bool {
return len(ocrConfigs) == 2 && !prevDeployment.HasCandidateInstance(pluginType)
}

func isPromotion(pluginType cctypes.PluginType, ocrConfigs []ccipreaderpkg.OCR3ConfigWithMeta, prevDeployment ccipDeployment) bool {
return len(ocrConfigs) == 1 && prevDeployment.HasCandidateInstance(pluginType)
}
Loading
Loading