Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions deployment/cre/forwarder/stellar/add_forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (

"github.com/Masterminds/semver/v3"

mcmstypes "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
stellardeployment "github.com/smartcontractkit/chainlink-stellar/deployment"
Expand All @@ -30,6 +32,8 @@ type AddForwardersRequest struct {
Chains map[uint64]struct{}
Qualifier string
Version string

MCMS *cldf.MCMSTimelockProposalInput
}

func (cs AddForwarders) VerifyPreconditions(env cldf.Environment, req *AddForwardersRequest) error {
Expand All @@ -49,6 +53,11 @@ func (cs AddForwarders) VerifyPreconditions(env cldf.Environment, req *AddForwar
if err != nil {
return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err)
}
if req.MCMS != nil {
if err := req.MCMS.Validate(); err != nil {
return fmt.Errorf("invalid MCMS timelock proposal input: %w", err)
}
}

chains := req.Chains
if len(chains) == 0 {
Expand Down Expand Up @@ -81,6 +90,8 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) (
}
}

var batchOps []mcmstypes.BatchOperation

for sel := range chains {
ch, ok := env.BlockChains.StellarChains()[sel]
if !ok {
Expand All @@ -93,6 +104,24 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) (
return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err)
}

// The governed path only reads (owner check) and encodes; it must not
// require the deployer signing key.
if req.MCMS != nil {
if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil {
return out, err
}

batchOp, err := addForwardersBatchOp(sel, addrRef.Address, req.Transmitters)
if err != nil {
return out, fmt.Errorf("failed to build add_forwarder batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err)
}
batchOps = append(batchOps, batchOp)

env.Logger.Infow("Built governed Stellar forwarder add_forwarder operations", "chainSelector", sel, "forwarder", addrRef.Address, "transmitters", len(req.Transmitters))

continue
}

deployer, err := stellardeployment.NewDeployerFromChain(ch)
if err != nil {
return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err)
Expand All @@ -106,5 +135,11 @@ func (cs AddForwarders) Apply(env cldf.Environment, req *AddForwardersRequest) (
}
}

if req.MCMS != nil {
return cldf.NewOutputBuilder(env, nil).
WithTimelockProposal(*req.MCMS, batchOps).
Build()
}

return out, nil
}
149 changes: 149 additions & 0 deletions deployment/cre/forwarder/stellar/clear_config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package stellar

import (
"errors"
"fmt"

"github.com/Masterminds/semver/v3"

mcmstypes "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
crebindings "github.com/smartcontractkit/chainlink-stellar/bindings/contracts/cre"
stellardeployment "github.com/smartcontractkit/chainlink-stellar/deployment"
)

var _ cldf.ChangeSetV2[*ClearConfigRequest] = ClearConfigs{}

// ClearConfigs removes a DON signer configuration from deployed Stellar CRE
// forwarders. Used to rotate out a bad or retired DON config; reports for the
// cleared (donID, configVersion) pair are rejected afterwards.
type ClearConfigs struct{}

type ClearConfigRequest struct {
DonID uint32
ConfigVersion uint32

// Chains is optional. When set, only those selectors are cleared.
Chains map[uint64]struct{}
Qualifier string
Version string

// MCMS, when set, builds a governed clear_config timelock proposal instead
// of sending directly with the deployer key. Required once the forwarder
// is owned by the MCMS timelock. Its Qualifier selects the MCMS instance.
MCMS *cldf.MCMSTimelockProposalInput
}

func (cs ClearConfigs) VerifyPreconditions(env cldf.Environment, req *ClearConfigRequest) error {
if req == nil {
return errors.New("request is required")
}
if req.DonID == 0 {
return errors.New("DON ID is required")
}
if req.ConfigVersion == 0 {
return errors.New("config version is required")
}
if req.Qualifier == "" {
return errors.New("forwarder qualifier is required")
}
if req.Version == "" {
return errors.New("forwarder version is required")
}
version, err := semver.NewVersion(req.Version)
if err != nil {
return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err)
}
if req.MCMS != nil {
if err := req.MCMS.Validate(); err != nil {
return fmt.Errorf("invalid MCMS timelock proposal input: %w", err)
}
}

chains := req.Chains
if len(chains) == 0 {
chains = make(map[uint64]struct{})
for sel := range env.BlockChains.StellarChains() {
chains[sel] = struct{}{}
}
}
for sel := range chains {
if _, ok := env.BlockChains.StellarChains()[sel]; !ok {
return fmt.Errorf("stellar chain not found for chain selector %d", sel)
}
refKey := datastore.NewAddressRefKey(sel, ForwarderContract, version, req.Qualifier)
if _, err := env.DataStore.Addresses().Get(refKey); err != nil {
return fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err)
}
}

return nil
}

func (cs ClearConfigs) Apply(env cldf.Environment, req *ClearConfigRequest) (cldf.ChangesetOutput, error) {
var out cldf.ChangesetOutput

version := semver.MustParse(req.Version)
chains := req.Chains
if len(chains) == 0 {
chains = make(map[uint64]struct{})
for sel := range env.BlockChains.StellarChains() {
chains[sel] = struct{}{}
}
}

var batchOps []mcmstypes.BatchOperation

for sel := range chains {
ch, ok := env.BlockChains.StellarChains()[sel]
if !ok {
return out, fmt.Errorf("stellar chain not found for chain selector %d", sel)
}

refKey := datastore.NewAddressRefKey(sel, ForwarderContract, version, req.Qualifier)
addrRef, err := env.DataStore.Addresses().Get(refKey)
if err != nil {
return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err)
}

// The governed path only reads (owner check) and encodes; it must not
// require the deployer signing key.
if req.MCMS != nil {
if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil {
return out, err
}

batchOp, err := forwarderClearConfigBatchOp(sel, addrRef.Address, req.DonID, req.ConfigVersion)
if err != nil {
return out, fmt.Errorf("failed to build clear_config batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err)
}
batchOps = append(batchOps, batchOp)

env.Logger.Infow("Built governed Stellar forwarder clear_config operation", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DonID, "configVersion", req.ConfigVersion)

continue
}

deployer, err := stellardeployment.NewDeployerFromChain(ch)
if err != nil {
return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err)
}

client := crebindings.NewForwarderClient(deployer, addrRef.Address)
if err := client.ClearConfig(env.GetContext(), req.DonID, req.ConfigVersion); err != nil {
return out, fmt.Errorf("failed to clear config on stellar forwarder %s (chain selector %d): %w", addrRef.Address, sel, err)
}

env.Logger.Infow("Cleared Stellar CRE forwarder config", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DonID, "configVersion", req.ConfigVersion)
}

if req.MCMS != nil {
return cldf.NewOutputBuilder(env, nil).
WithTimelockProposal(*req.MCMS, batchOps).
Build()
}

return out, nil
}
38 changes: 38 additions & 0 deletions deployment/cre/forwarder/stellar/configure_forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/Masterminds/semver/v3"
chainselectors "github.com/smartcontractkit/chain-selectors"

mcmstypes "github.com/smartcontractkit/mcms/types"

"github.com/smartcontractkit/chainlink-deployments-framework/datastore"
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
"github.com/smartcontractkit/chainlink-deployments-framework/offchain"
Expand Down Expand Up @@ -36,6 +38,11 @@ type ConfigureForwarderRequest struct {
Chains map[uint64]struct{}
Qualifier string
Version string

// MCMS, when set, builds a governed set_config timelock proposal instead
// of sending directly with the deployer key. Required once the forwarder
// is owned by the MCMS timelock. Its Qualifier selects the MCMS instance.
MCMS *cldf.MCMSTimelockProposalInput
}

func (cs ConfigureForwarders) VerifyPreconditions(env cldf.Environment, req *ConfigureForwarderRequest) error {
Expand All @@ -58,6 +65,11 @@ func (cs ConfigureForwarders) VerifyPreconditions(env cldf.Environment, req *Con
if err != nil {
return fmt.Errorf("invalid forwarder version %q: %w", req.Version, err)
}
if req.MCMS != nil {
if err := req.MCMS.Validate(); err != nil {
return fmt.Errorf("invalid MCMS timelock proposal input: %w", err)
}
}

chains := req.Chains
if len(chains) == 0 {
Expand Down Expand Up @@ -100,6 +112,8 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde
return out, fmt.Errorf("no stellar signers found for DON %q", req.DON.Name)
}

var batchOps []mcmstypes.BatchOperation

for sel := range chains {
ch, ok := env.BlockChains.StellarChains()[sel]
if !ok {
Expand All @@ -112,6 +126,24 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde
return out, fmt.Errorf("failed to get stellar forwarder for ref key %s: %w", refKey, err)
}

// The governed path only reads (owner check) and encodes; it must not
// require the deployer signing key.
if req.MCMS != nil {
if err := requireTimelockOwnership(env.GetContext(), env, ch, addrRef.Address, *req.MCMS); err != nil {
return out, err
}

batchOp, err := forwarderSetConfigBatchOp(sel, addrRef.Address, req.DON.ID, req.DON.Version, uint32(req.DON.F), signers)
if err != nil {
return out, fmt.Errorf("failed to build set_config batch operation for stellar forwarder %s on chain selector %d: %w", addrRef.Address, sel, err)
}
batchOps = append(batchOps, batchOp)

env.Logger.Infow("Built governed Stellar forwarder set_config operation", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DON.ID, "f", req.DON.F, "signersLen", len(signers))

continue
}

deployer, err := stellardeployment.NewDeployerFromChain(ch)
if err != nil {
return out, fmt.Errorf("failed to build stellar deployer for chain selector %d: %w", sel, err)
Expand All @@ -132,6 +164,12 @@ func (cs ConfigureForwarders) Apply(env cldf.Environment, req *ConfigureForwarde
env.Logger.Infow("Configured Stellar CRE forwarder", "chainSelector", sel, "forwarder", addrRef.Address, "donID", req.DON.ID, "f", req.DON.F, "signersLen", len(signers))
}

if req.MCMS != nil {
return cldf.NewOutputBuilder(env, nil).
WithTimelockProposal(*req.MCMS, batchOps).
Build()
}

return out, nil
}

Expand Down
Loading
Loading