-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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 helper to use in cli in CLD #15647
Changes from 4 commits
95bf07c
09997ec
25912d9
fa91ed1
ae0c33e
549b3a7
4880e5e
8d5063e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
package changeset | ||
krehermann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
|
||
"github.com/ethereum/go-ethereum/accounts/abi/bind" | ||
"github.com/ethereum/go-ethereum/common" | ||
owner_helpers "github.com/smartcontractkit/ccip-owner-contracts/pkg/gethwrappers" | ||
"github.com/smartcontractkit/ccip-owner-contracts/pkg/proposal/mcms" | ||
"github.com/smartcontractkit/chainlink-common/pkg/logger" | ||
"github.com/smartcontractkit/chainlink/deployment" | ||
"github.com/smartcontractkit/chainlink/deployment/common/types" | ||
) | ||
|
||
// TimelockExecutionContracts is a helper struct for executing timelock proposals. it contains | ||
// the timelock and call proxy contracts. | ||
type TimelockExecutionContracts struct { | ||
Timelock *owner_helpers.RBACTimelock | ||
CallProxy *owner_helpers.CallProxy | ||
} | ||
|
||
// NewTimelockExecutionContracts creates a new TimelockExecutionContracts struct. | ||
krehermann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// If there are multiple timelocks or call proxy on the chain, an error is returned. | ||
// If there is a missing timelocks or call proxy on the chain, an error is returned. | ||
func NewTimelockExecutionContracts(env deployment.Environment, chainSelector uint64) (*TimelockExecutionContracts, error) { | ||
addrTypeVer, err := env.ExistingAddresses.AddressesForChain(chainSelector) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting addresses for chain: %w", err) | ||
} | ||
var timelock *owner_helpers.RBACTimelock | ||
var callProxy *owner_helpers.CallProxy | ||
for addr, tv := range addrTypeVer { | ||
if tv.Type == types.RBACTimelock { | ||
if timelock != nil { | ||
return nil, fmt.Errorf("multiple timelocks found on chain %d", chainSelector) | ||
} | ||
var err error | ||
timelock, err = owner_helpers.NewRBACTimelock(common.HexToAddress(addr), env.Chains[chainSelector].Client) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating timelock: %w", err) | ||
} | ||
} | ||
if tv.Type == types.CallProxy { | ||
if callProxy != nil { | ||
return nil, fmt.Errorf("multiple call proxies found on chain %d", chainSelector) | ||
} | ||
var err error | ||
callProxy, err = owner_helpers.NewCallProxy(common.HexToAddress(addr), env.Chains[chainSelector].Client) | ||
if err != nil { | ||
return nil, fmt.Errorf("error creating call proxy: %w", err) | ||
} | ||
} | ||
} | ||
if timelock == nil || callProxy == nil { | ||
return nil, fmt.Errorf("missing timelock (%T) or call proxy(%T) on chain %d", timelock == nil, callProxy == nil, chainSelector) | ||
} | ||
return &TimelockExecutionContracts{ | ||
Timelock: timelock, | ||
CallProxy: callProxy, | ||
}, nil | ||
} | ||
|
||
type RunTimelockExecutorConfig struct { | ||
Executor *mcms.Executor | ||
TimelockContracts *TimelockExecutionContracts | ||
ChainSelector uint64 | ||
// BlockStart is optional. It filter the timelock scheduled events. | ||
// If not provided, the executor assumes that the operations have not been executed yet | ||
// executes all the operations for the given chain. | ||
BlockStart *uint64 | ||
BlockEnd *uint64 | ||
} | ||
|
||
func (cfg RunTimelockExecutorConfig) Validate() error { | ||
if cfg.Executor == nil { | ||
krehermann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return fmt.Errorf("executor is nil") | ||
} | ||
if cfg.TimelockContracts == nil { | ||
return fmt.Errorf("timelock contracts is nil") | ||
} | ||
if cfg.ChainSelector == 0 { | ||
return fmt.Errorf("chain selector is 0") | ||
} | ||
if cfg.BlockStart != nil && cfg.BlockEnd == nil { | ||
if *cfg.BlockStart > *cfg.BlockEnd { | ||
return fmt.Errorf("block start is greater than block end") | ||
} | ||
} | ||
if cfg.BlockStart == nil && cfg.BlockEnd != nil { | ||
return fmt.Errorf("block start must not be nil when block end is not nil") | ||
} | ||
|
||
if len(cfg.Executor.Operations[mcms.ChainIdentifier(cfg.ChainSelector)]) == 0 { | ||
return fmt.Errorf("no operations for chain %d", cfg.ChainSelector) | ||
} | ||
return nil | ||
} | ||
|
||
// RunTimelockExecutor runs the scheduled operations for the given chain. | ||
// If the block start is not provided, it assumes that the operations have not been scheduled yet | ||
// and executes all the operations for the given chain. | ||
// It is an error if there are no operations for the given chain. | ||
func RunTimelockExecutor(env deployment.Environment, cfg RunTimelockExecutorConfig) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sigh. It makes me nervous having this all accessible to changesets, as I want to avoid changesets themselves from ever executing this logic. But I guess this is fine for now - I agree, it probably ought to go into MCMS library though. We can move it there later. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do either of these make it better:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think both help, yeah. |
||
// TODO: This sort of helper probably should move to the MCMS lib. | ||
// Execute all the transactions in the proposal which are for this chain. | ||
if err := cfg.Validate(); err != nil { | ||
return fmt.Errorf("error validating config: %w", err) | ||
} | ||
for _, chainOp := range cfg.Executor.Operations[mcms.ChainIdentifier(cfg.ChainSelector)] { | ||
for idx, op := range cfg.Executor.ChainAgnosticOps { | ||
start := cfg.BlockStart | ||
end := cfg.BlockEnd | ||
if bytes.Equal(op.Data, chainOp.Data) && op.To == chainOp.To { | ||
if start == nil { | ||
opTx, err2 := cfg.Executor.ExecuteOnChain(env.Chains[cfg.ChainSelector].Client, env.Chains[cfg.ChainSelector].DeployerKey, idx) | ||
if err2 != nil { | ||
return fmt.Errorf("error executing on chain: %w", err2) | ||
} | ||
block, err2 := env.Chains[cfg.ChainSelector].Confirm(opTx) | ||
if err2 != nil { | ||
return fmt.Errorf("error confirming on chain: %w", err2) | ||
} | ||
start = &block | ||
end = &block | ||
} | ||
|
||
it, err2 := cfg.TimelockContracts.Timelock.FilterCallScheduled(&bind.FilterOpts{ | ||
Start: *start, | ||
End: end, | ||
Context: env.GetContext(), | ||
}, nil, nil) | ||
if err2 != nil { | ||
return fmt.Errorf("error filtering call scheduled: %w", err2) | ||
} | ||
var calls []owner_helpers.RBACTimelockCall | ||
var pred, salt [32]byte | ||
for it.Next() { | ||
// Note these are the same for the whole batch, can overwrite | ||
pred = it.Event.Predecessor | ||
salt = it.Event.Salt | ||
verboseDebug(env.Logger, it.Event) | ||
env.Logger.Info("scheduled", "event", it.Event) | ||
calls = append(calls, owner_helpers.RBACTimelockCall{ | ||
Target: it.Event.Target, | ||
Data: it.Event.Data, | ||
Value: it.Event.Value, | ||
}) | ||
} | ||
|
||
timelockExecutorProxy, err := owner_helpers.NewRBACTimelock(cfg.TimelockContracts.CallProxy.Address(), env.Chains[cfg.ChainSelector].Client) | ||
if err != nil { | ||
return fmt.Errorf("error creating timelock executor proxy: %w", err) | ||
} | ||
tx, err := timelockExecutorProxy.ExecuteBatch( | ||
env.Chains[cfg.ChainSelector].DeployerKey, calls, pred, salt) | ||
if err != nil { | ||
return fmt.Errorf("error executing batch: %w", err) | ||
} | ||
_, err = env.Chains[cfg.ChainSelector].Confirm(tx) | ||
if err != nil { | ||
return fmt.Errorf("error confirming batch: %w", err) | ||
} | ||
} | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
func verboseDebug(lggr logger.Logger, event *owner_helpers.RBACTimelockCallScheduled) { | ||
b, err := json.Marshal(event) | ||
if err != nil { | ||
panic(err) | ||
} | ||
lggr.Debug("scheduled", "event", string(b)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -91,6 +91,7 @@ func ApplyChangesets(t *testing.T, e deployment.Environment, timelockContractsPe | |
NodeIDs: e.NodeIDs, | ||
Offchain: e.Offchain, | ||
OCRSecrets: e.OCRSecrets, | ||
GetContext: e.GetContext, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dammit, another one. Shouldn't we be using NewEnvironment here instead of priming the struct anyway? Sigh. (Not a review, just drive-by frustration) |
||
} | ||
} | ||
return currentEnv, nil | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ci is timing out. parallelize many ccip tests