diff --git a/core/services/ocr2/plugins/ccip/ccipexec/ocr2.go b/core/services/ocr2/plugins/ccip/ccipexec/ocr2.go index 7b4d75af31b..cd77fe49ad4 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/ocr2.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/ocr2.go @@ -226,12 +226,49 @@ func (r *ExecutionReportingPlugin) getExecutableObservations(ctx context.Context lggr.Infow("Execution batch created", "batchSize", len(batch), "messageStates", msgExecStates) return batch, nil } - r.commitRootsCache.Snooze(merkleRoot) + // Skip snoozing if we have token data delays but no serious processing errors + shouldSkipSnooze := shouldSkipSnoozeForTokenDataNotReady(msgExecStates) + + if shouldSkipSnooze { + rootLggr.Infow("Skipping snooze - messages waiting for token data with no processing errors") + } else { + r.commitRootsCache.Snooze(merkleRoot) + } } } return []ccip.ObservedMessage{}, nil } +// shouldSkipSnoozeForTokenDataNotReady checks if we should skip snoozing for token data not ready. +// If there are token data delays but no serious processing errors, we skip snoozing. +func shouldSkipSnoozeForTokenDataNotReady(msgExecStates []messageExecStatus) bool { + if len(msgExecStates) == 0 { + return false + } + + hasTokenDataNotReady := false + hasProcessingErrors := false + + harmlessStatuses := map[messageStatus]bool{ + AlreadyExecuted: true, + TokenDataNotReady: true, + SkippedInflight: true, + AddedToBatch: true, + SuccesfullyValidated: true, + } + + for _, state := range msgExecStates { + if state.Status == TokenDataNotReady { + hasTokenDataNotReady = true + } + if !harmlessStatuses[state.Status] { + hasProcessingErrors = true + } + } + + return hasTokenDataNotReady && !hasProcessingErrors +} + // Calculates a map that indicates whether a sequence number has already been executed. // It doesn't matter if the execution succeeded, since we don't retry previous // attempts even if they failed. Value in the map indicates whether the log is finalized or not. diff --git a/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go b/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go index f01cf0b9458..66188d2e603 100644 --- a/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go +++ b/core/services/ocr2/plugins/ccip/ccipexec/ocr2_test.go @@ -1475,3 +1475,74 @@ func TestExecutionReportingPlugin_getConsensusThreshold(t *testing.T) { }) } } + +func TestExecutionReportingPlugin_SnoozeLogicWithTokenDataNotReady(t *testing.T) { + testCases := []struct { + name string + msgExecStates []messageExecStatus + shouldSnooze bool + description string + }{ + { + name: "all messages have TokenDataNotReady - should NOT snooze", + msgExecStates: []messageExecStatus{ + {SeqNr: 1, MessageId: "0x1", Status: TokenDataNotReady}, + {SeqNr: 2, MessageId: "0x2", Status: TokenDataNotReady}, + {SeqNr: 3, MessageId: "0x3", Status: TokenDataNotReady}, + }, + shouldSnooze: false, + description: "When all messages are waiting for token data, retry quickly instead of snoozing", + }, + { + name: "mixed harmless statuses including TokenDataNotReady - should NOT snooze", + msgExecStates: []messageExecStatus{ + {SeqNr: 1, MessageId: "0x1", Status: TokenDataNotReady}, + {SeqNr: 2, MessageId: "0x2", Status: AlreadyExecuted}, + {SeqNr: 3, MessageId: "0x3", Status: TokenDataNotReady}, + }, + shouldSnooze: false, + description: "When messages have only harmless issues including TokenDataNotReady, don't snooze", + }, + { + name: "mixed statuses with processing errors - should snooze", + msgExecStates: []messageExecStatus{ + {SeqNr: 1, MessageId: "0x1", Status: TokenDataNotReady}, + {SeqNr: 2, MessageId: "0x2", Status: InvalidNonce}, + {SeqNr: 3, MessageId: "0x3", Status: TokenDataNotReady}, + }, + shouldSnooze: true, + description: "When messages have processing errors alongside TokenDataNotReady, snooze as before", + }, + { + name: "no TokenDataNotReady statuses - should snooze", + msgExecStates: []messageExecStatus{ + {SeqNr: 1, MessageId: "0x1", Status: AlreadyExecuted}, + {SeqNr: 2, MessageId: "0x2", Status: InvalidNonce}, + {SeqNr: 3, MessageId: "0x3", Status: InsufficientRemainingBatchGas}, + }, + shouldSnooze: true, + description: "When messages have other issues, snooze as before", + }, + { + name: "single message with TokenDataNotReady - should NOT snooze", + msgExecStates: []messageExecStatus{ + {SeqNr: 1, MessageId: "0x1", Status: TokenDataNotReady}, + }, + shouldSnooze: false, + description: "Single message waiting for token data should retry quickly", + }, + { + name: "empty message states - should snooze", + msgExecStates: []messageExecStatus{}, + shouldSnooze: true, + description: "No messages means unknown issue, should snooze", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + shouldSkipSnooze := shouldSkipSnoozeForTokenDataNotReady(tc.msgExecStates) + require.Equal(t, tc.shouldSnooze, !shouldSkipSnooze) + }) + } +}