Skip to content

Commit 3965c2d

Browse files
feat: add IsOperationDone and IsChainDone to timelock executable (#337)
In order to support concurrent timelock executions between the chainlink-deployments CI and the legacy timelock-worker service, the GHA running on the CI need to be able to check if an operation and/or the operations of a chain have been marked as "done" by the timelock worker. This PR adds two methods to the TimelockExecutable type, implementing these two checks. The implementation was adapted from the existing methods: `IsOperationReady` and `IsChainReady`. JIRA: [DPA-1613](https://smartcontract-it.atlassian.net/browse/DPA-1613) [DPA-1613]: https://smartcontract-it.atlassian.net/browse/DPA-1613?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
1 parent b7a1cbc commit 3965c2d

File tree

4 files changed

+79
-10
lines changed

4 files changed

+79
-10
lines changed

.changeset/tall-tigers-wave.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smartcontractkit/mcms": minor
3+
---
4+
5+
feat: add IsOperationDone and IsChainDone to TimelockExecutable

errors.go

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,16 @@ func (e *OperationNotReadyError) Error() string {
1919
return fmt.Sprintf("operation %d is not ready", e.OpIndex)
2020
}
2121

22+
// OperationNotDoneError is returned when an operation is not yet done.
23+
type OperationNotDoneError struct {
24+
OpIndex int
25+
}
26+
27+
// Error implements the error interface.
28+
func (e *OperationNotDoneError) Error() string {
29+
return fmt.Sprintf("operation %d is not done", e.OpIndex)
30+
}
31+
2232
// InvalidProposalKindError is returned when an invalid proposal kind is provided.
2333
type InvalidProposalKindError struct {
2434
ProvidedKind types.ProposalKind

timelock_executable.go

+37
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,43 @@ func (t *TimelockExecutable) IsOperationReady(ctx context.Context, idx int) erro
131131
return nil
132132
}
133133

134+
// IsChainDone checks if the chain is done executing
135+
func (t *TimelockExecutable) IsChainDone(ctx context.Context, chainSelector types.ChainSelector) error {
136+
// Check readiness for each global operation in the proposal
137+
for globalIndex, op := range t.proposal.Operations {
138+
if op.ChainSelector == chainSelector {
139+
err := t.IsOperationDone(ctx, globalIndex)
140+
if err != nil {
141+
return err
142+
}
143+
}
144+
}
145+
146+
return nil
147+
}
148+
149+
func (t *TimelockExecutable) IsOperationDone(ctx context.Context, idx int) error {
150+
op := t.proposal.Operations[idx]
151+
152+
cs := op.ChainSelector
153+
timelock := t.proposal.TimelockAddresses[cs]
154+
155+
operationID, err := t.GetOpID(ctx, idx, op, cs)
156+
if err != nil {
157+
return fmt.Errorf("unable to get operation ID: %w", err)
158+
}
159+
160+
isDone, err := t.executors[cs].IsOperationDone(ctx, timelock, operationID)
161+
if err != nil {
162+
return err
163+
}
164+
if !isDone {
165+
return &OperationNotDoneError{OpIndex: idx}
166+
}
167+
168+
return nil
169+
}
170+
134171
type Option func(*executeOptions)
135172

136173
type executeOptions struct {

timelock_executable_test.go

+27-10
Original file line numberDiff line numberDiff line change
@@ -573,27 +573,44 @@ func scheduleAndExecuteGrantRolesProposal(t *testing.T, ctx context.Context, tar
573573
require.NoError(t, sim.Backend.AdjustTime(5*time.Second))
574574
sim.Backend.Commit() // Note < 1.14 geth needs a commit after adjusting time.
575575

576-
// Check that the operation is now ready
576+
opIdx := 0
577+
578+
// IsReady
577579
err = tExecutable.IsReady(ctx)
578580
require.NoError(t, err)
579-
580-
// Check IsChainReady function succeeds
581581
for chainSelector := range proposal.ChainMetadata {
582582
err = tExecutable.IsChainReady(ctx, chainSelector)
583583
require.NoError(t, err)
584584
}
585585

586+
// !IsDone
587+
err = tExecutable.IsOperationDone(ctx, opIdx)
588+
require.Error(t, err)
589+
for chainSelector := range proposal.ChainMetadata {
590+
err = tExecutable.IsChainDone(ctx, chainSelector)
591+
require.Error(t, err)
592+
}
593+
586594
// Execute the proposal
587-
idx := 0
588-
_, err = tExecutable.Execute(ctx, idx)
595+
_, err = tExecutable.Execute(ctx, opIdx)
589596
require.NoError(t, err)
590597
sim.Backend.Commit()
591-
opID, err := tExecutable.GetOpID(ctx, idx, proposal.Operations[idx], proposal.Operations[idx].ChainSelector)
592-
require.NoError(t, err)
593-
// Check that the operation is done
594-
isOperationDone, err := timelockC.IsOperationDone(&bind.CallOpts{}, opID)
598+
599+
// IsDone
600+
err = tExecutable.IsOperationDone(ctx, opIdx)
595601
require.NoError(t, err)
596-
require.True(t, isOperationDone)
602+
for chainSelector := range proposal.ChainMetadata {
603+
err = tExecutable.IsChainDone(ctx, chainSelector)
604+
require.NoError(t, err)
605+
}
606+
607+
// !IsReady
608+
err = tExecutable.IsOperationReady(ctx, opIdx)
609+
require.Error(t, err)
610+
for chainSelector := range proposal.ChainMetadata {
611+
err = tExecutable.IsChainReady(ctx, chainSelector)
612+
require.Error(t, err)
613+
}
597614

598615
// Check the state of the timelock contract
599616
for _, role := range targetRoles {

0 commit comments

Comments
 (0)