diff --git a/.github/workflows/ccip-ocr3-build-lint-test.yml b/.github/workflows/ccip-ocr3-build-lint-test.yml index e04d02ada..751b597c6 100644 --- a/.github/workflows/ccip-ocr3-build-lint-test.yml +++ b/.github/workflows/ccip-ocr3-build-lint-test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - go-version: ['1.22'] + go-version: ['1.21', '1.22'] defaults: run: working-directory: . diff --git a/execute/exectypes/observation.go b/execute/exectypes/observation.go index 92dc9baa4..f75a318cd 100644 --- a/execute/exectypes/observation.go +++ b/execute/exectypes/observation.go @@ -8,6 +8,7 @@ import ( type CommitObservations map[cciptypes.ChainSelector][]CommitData type MessageObservations map[cciptypes.ChainSelector]map[cciptypes.SeqNum]cciptypes.Message +type NonceObservations map[cciptypes.ChainSelector]map[string]uint64 // Observation is the observation of the ExecutePlugin. // TODO: revisit observation types. The maps used here are more space efficient and easier to work @@ -16,16 +17,20 @@ type Observation struct { // CommitReports are determined during the first phase of execute. // It contains the commit reports we would like to execute in the following round. CommitReports CommitObservations `json:"commitReports"` + // Messages are determined during the second phase of execute. // Ideally, it contains all the messages identified by the previous outcome's // NextCommits. With the previous outcome, and these messsages, we can build the // execute report. Messages MessageObservations `json:"messages"` - // TODO: some of the nodes configuration may need to be included here. + + // Nonces are determined during the third phase of execute. + // It contains the nonces of senders who are being considered for the final report. + Nonces NonceObservations `json:"nonces"` } func NewObservation( - commitReports CommitObservations, messages MessageObservations) Observation { + commitReports CommitObservations, messages MessageObservations, nonces NonceObservations) Observation { return Observation{ CommitReports: commitReports, Messages: messages, @@ -37,6 +42,9 @@ func (obs Observation) Encode() ([]byte, error) { } func DecodeObservation(b []byte) (Observation, error) { + if len(b) == 0 { + return Observation{}, nil + } obs := Observation{} err := json.Unmarshal(b, &obs) return obs, err diff --git a/execute/exectypes/outcome.go b/execute/exectypes/outcome.go index 20dda0053..92712aea9 100644 --- a/execute/exectypes/outcome.go +++ b/execute/exectypes/outcome.go @@ -30,8 +30,7 @@ func (p PluginState) Next() PluginState { return GetMessages case GetMessages: - // TODO: go to Filter after GetMessages - return GetCommitReports + return Filter case Unknown: fallthrough diff --git a/execute/exectypes/outcome_test.go b/execute/exectypes/outcome_test.go index 35dd376b7..d8f6ace46 100644 --- a/execute/exectypes/outcome_test.go +++ b/execute/exectypes/outcome_test.go @@ -24,8 +24,13 @@ func TestPluginState_Next(t *testing.T) { want: GetMessages, }, { - name: "Phase 2 to 1", + name: "Phase 2 to 3", p: GetMessages, + want: Filter, + }, + { + name: "Phase 3 to 1", + p: Filter, want: GetCommitReports, }, { diff --git a/execute/plugin.go b/execute/plugin.go index 7a75fcf35..65454db32 100644 --- a/execute/plugin.go +++ b/execute/plugin.go @@ -7,6 +7,7 @@ import ( "time" mapset "github.com/deckarep/golang-set/v2" + "golang.org/x/exp/maps" "github.com/smartcontractkit/libocr/commontypes" "github.com/smartcontractkit/libocr/offchainreporting2plus/ocr3types" @@ -168,6 +169,7 @@ func (p *Plugin) Observation( } state := previousOutcome.State.Next() + p.lggr.Debugw("Execute plugin performing observation.", "state", state) switch state { case exectypes.GetCommitReports: fetchFrom := time.Now().Add(-p.cfg.OffchainConfig.MessageVisibilityInterval.Duration()).UTC() @@ -185,7 +187,7 @@ func (p *Plugin) Observation( } // TODO: truncate grouped to a maximum observation size? - return exectypes.NewObservation(groupedCommits, nil).Encode() + return exectypes.NewObservation(groupedCommits, nil, nil).Encode() } // No observation for non-dest readers. @@ -203,7 +205,7 @@ func (p *Plugin) Observation( commitReportCache[report.SourceChain] = append(commitReportCache[report.SourceChain], report) } - for selector, reports := range commitReportCache { + for srcChain, reports := range commitReportCache { if len(reports) == 0 { continue } @@ -215,15 +217,16 @@ func (p *Plugin) Observation( // Read messages for each range. for _, seqRange := range ranges { - msgs, err := p.ccipReader.MsgsBetweenSeqNums(ctx, selector, seqRange) + // TODO: check if srcChain is supported. + msgs, err := p.ccipReader.MsgsBetweenSeqNums(ctx, srcChain, seqRange) if err != nil { return nil, err } for _, msg := range msgs { - if _, ok := messages[selector]; !ok { - messages[selector] = make(map[cciptypes.SeqNum]cciptypes.Message) + if _, ok := messages[srcChain]; !ok { + messages[srcChain] = make(map[cciptypes.SeqNum]cciptypes.Message) } - messages[selector][msg.Header.SequenceNumber] = msg + messages[srcChain][msg.Header.SequenceNumber] = msg } } } @@ -240,11 +243,35 @@ func (p *Plugin) Observation( } // TODO: Fire off messages for an attestation check service. - return exectypes.NewObservation(groupedCommits, messages).Encode() + return exectypes.NewObservation(groupedCommits, messages, nil).Encode() case exectypes.Filter: - // TODO: pass the previous two through, add in the nonces. - return types.Observation{}, fmt.Errorf("unknown state") + // TODO: add in nonces, other data comes from previous outcome. + nonceRequestArgs := make(map[cciptypes.ChainSelector]map[string]struct{}) + + // Collect unique senders. + for _, commitReport := range previousOutcome.Report.ChainReports { + if _, ok := nonceRequestArgs[commitReport.SourceChainSelector]; !ok { + nonceRequestArgs[commitReport.SourceChainSelector] = make(map[string]struct{}) + } + for _, msg := range commitReport.Messages { + nonceRequestArgs[commitReport.SourceChainSelector][msg.Sender.String()] = struct{}{} + } + } + + // Read args from chain. + nonceObservations := make(exectypes.NonceObservations) + for srcChain, addrSet := range nonceRequestArgs { + // TODO: check if srcSelector is supported. + addrs := maps.Keys(addrSet) + nonces, err := p.ccipReader.Nonces(ctx, srcChain, p.cfg.DestChain, addrs) + if err != nil { + return types.Observation{}, fmt.Errorf("unable to get nonces: %w", err) + } + nonceObservations[srcChain] = nonces + } + + return exectypes.NewObservation(nil, nil, nonceObservations).Encode() default: return types.Observation{}, fmt.Errorf("unknown state") } @@ -292,6 +319,7 @@ func selectReport( encoder cciptypes.ExecutePluginCodec, tokenDataReader exectypes.TokenDataReader, estimateProvider gas.EstimateProvider, + nonces map[cciptypes.ChainSelector]map[string]uint64, commitReports []exectypes.CommitData, maxReportSizeBytes int, maxGas uint64, @@ -306,6 +334,7 @@ func selectReport( tokenDataReader, encoder, estimateProvider, + nonces, uint64(maxReportSizeBytes), maxGas) var stillPendingReports []exectypes.CommitData @@ -390,31 +419,30 @@ func (p *Plugin) Outcome( observation := exectypes.NewObservation( mergedCommitObservations, - mergedMessageObservations) - - ////////////////////////// - // common preprocessing // - ////////////////////////// - - // flatten commit reports and sort by timestamp. - var commitReports []exectypes.CommitData - for _, report := range observation.CommitReports { - commitReports = append(commitReports, report...) - } - sort.Slice(commitReports, func(i, j int) bool { - return commitReports[i].Timestamp.Before(commitReports[j].Timestamp) - }) - - p.lggr.Debugw( - fmt.Sprintf("[oracle %d] exec outcome: commit reports", p.reportingCfg.OracleID), - "commitReports", commitReports) + mergedMessageObservations, + nil) state := previousOutcome.State.Next() switch state { case exectypes.GetCommitReports: + // flatten commit reports and sort by timestamp. + var commitReports []exectypes.CommitData + for _, report := range observation.CommitReports { + commitReports = append(commitReports, report...) + } + sort.Slice(commitReports, func(i, j int) bool { + return commitReports[i].Timestamp.Before(commitReports[j].Timestamp) + }) + + p.lggr.Debugw( + fmt.Sprintf("[oracle %d] exec outcome: commit reports", p.reportingCfg.OracleID), + "commitReports", commitReports) + outcome := exectypes.NewOutcome(state, commitReports, cciptypes.ExecutePluginReport{}) return outcome.Encode() case exectypes.GetMessages: + commitReports := previousOutcome.PendingCommitReports + // add messages to their commitReports. for i, report := range commitReports { report.Messages = nil @@ -426,17 +454,31 @@ func (p *Plugin) Outcome( commitReports[i].Messages = report.Messages } + outcome := exectypes.NewOutcome(state, commitReports, cciptypes.ExecutePluginReport{}) + if outcome.IsEmpty() { + return nil, nil + } + + p.lggr.Infow( + fmt.Sprintf("[oracle %d] exec outcome: generated outcome", p.reportingCfg.OracleID), + "outcome", outcome) + + return outcome.Encode() + case exectypes.Filter: + commitReports := previousOutcome.PendingCommitReports + // TODO: this function should be pure, a context should not be needed. - outcomeReports, commitReports, err := - selectReport( - context.Background(), - p.lggr, p.msgHasher, - p.reportCodec, - p.tokenDataReader, - p.estimateProvider, - commitReports, - maxReportSizeBytes, - p.cfg.OffchainConfig.BatchGasLimit) + outcomeReports, commitReports, err := selectReport( + context.Background(), + p.lggr, + p.msgHasher, + p.reportCodec, + p.tokenDataReader, + p.estimateProvider, + observation.Nonces, + commitReports, + maxReportSizeBytes, + p.cfg.OffchainConfig.BatchGasLimit) if err != nil { return ocr3types.Outcome{}, fmt.Errorf("unable to extract proofs: %w", err) } @@ -455,8 +497,7 @@ func (p *Plugin) Outcome( "outcome", outcome) return outcome.Encode() - case exectypes.Filter: - panic("not implemented") + default: panic("unknown state") } diff --git a/execute/plugin_e2e_test.go b/execute/plugin_e2e_test.go index b58612bb5..138973bce 100644 --- a/execute/plugin_e2e_test.go +++ b/execute/plugin_e2e_test.go @@ -50,7 +50,8 @@ func TestPlugin(t *testing.T) { runner := testhelpers.NewOCR3Runner(nodes, nodeIDs, nil) - // In the first round there is a pending commit report only. + // Round 1. + // One pending commit report only. // Two of the messages are executed which should be indicated in the Outcome. res, err := runner.RunRound(ctx) require.NoError(t, err) @@ -60,18 +61,26 @@ func TestPlugin(t *testing.T) { require.Len(t, outcome.PendingCommitReports, 1) require.ElementsMatch(t, outcome.PendingCommitReports[0].ExecutedMessages, []cciptypes.SeqNum{100, 101}) - // In the second round there is an exec report and the pending commit report is removed. - // The exec report should indicate the following messages are executed: 102, 103, 104, 105. + // Round 2. + // Messages now attached to the pending commit. + res, err = runner.RunRound(ctx) + require.NoError(t, err) + outcome, err = exectypes.DecodeOutcome(res.Outcome) + require.NoError(t, err) + require.Len(t, outcome.Report.ChainReports, 0) + require.Len(t, outcome.PendingCommitReports, 1) + + // Round 3. + // An execute report with the following messages executed: 102, 103, 104, 105. res, err = runner.RunRound(ctx) require.NoError(t, err) outcome, err = exectypes.DecodeOutcome(res.Outcome) require.NoError(t, err) - require.Len(t, outcome.Report.ChainReports, 1) - require.Len(t, outcome.PendingCommitReports, 0) sequenceNumbers := slicelib.Map(outcome.Report.ChainReports[0].Messages, func(m cciptypes.Message) cciptypes.SeqNum { return m.Header.SequenceNumber }) require.ElementsMatch(t, sequenceNumbers, []cciptypes.SeqNum{102, 103, 104, 105}) + } type nodeSetup struct { diff --git a/execute/plugin_test.go b/execute/plugin_test.go index 3119940ce..e139a797c 100644 --- a/execute/plugin_test.go +++ b/execute/plugin_test.go @@ -230,7 +230,7 @@ func TestPlugin_ValidateObservation_IneligibleObserver(t *testing.T) { }, }, }, - }) + }, nil) encoded, err := observation.Encode() require.NoError(t, err) err = p.ValidateObservation(ocr3types.OutcomeContext{}, types.Query{}, types.AttributedObservation{ @@ -263,7 +263,7 @@ func TestPlugin_ValidateObservation_ValidateObservedSeqNum_Error(t *testing.T) { {MerkleRoot: root}, }, } - observation := exectypes.NewObservation(commitReports, nil) + observation := exectypes.NewObservation(commitReports, nil, nil) encoded, err := observation.Encode() require.NoError(t, err) err = p.ValidateObservation(ocr3types.OutcomeContext{}, types.Query{}, types.AttributedObservation{ @@ -350,7 +350,7 @@ func TestPlugin_Outcome_CommitReportsMergeError(t *testing.T) { commitReports := map[cciptypes.ChainSelector][]exectypes.CommitData{ 1: {}, } - observation, err := exectypes.NewObservation(commitReports, nil).Encode() + observation, err := exectypes.NewObservation(commitReports, nil, nil).Encode() require.NoError(t, err) _, err = p.Outcome(ocr3types.OutcomeContext{}, nil, []types.AttributedObservation{ { @@ -383,7 +383,7 @@ func TestPlugin_Outcome_MessagesMergeError(t *testing.T) { }, }, } - observation, err := exectypes.NewObservation(nil, messages).Encode() + observation, err := exectypes.NewObservation(nil, messages, nil).Encode() require.NoError(t, err) _, err = p.Outcome(ocr3types.OutcomeContext{}, nil, []types.AttributedObservation{ { diff --git a/execute/report/builder.go b/execute/report/builder.go index 912d03166..abb554fe2 100644 --- a/execute/report/builder.go +++ b/execute/report/builder.go @@ -26,6 +26,7 @@ func NewBuilder( tokenDataReader exectypes.TokenDataReader, encoder cciptypes.ExecutePluginCodec, estimateProvider gas.EstimateProvider, + nonces map[cciptypes.ChainSelector]map[string]uint64, maxReportSizeBytes uint64, maxGas uint64, ) ExecReportBuilder { @@ -37,6 +38,8 @@ func NewBuilder( encoder: encoder, hasher: hasher, estimateProvider: estimateProvider, + sendersNonce: nonces, + expectedNonce: make(map[cciptypes.ChainSelector]map[string]uint64), maxReportSizeBytes: maxReportSizeBytes, maxGas: maxGas, @@ -65,6 +68,7 @@ type execReportBuilder struct { encoder cciptypes.ExecutePluginCodec hasher cciptypes.MessageHasher estimateProvider gas.EstimateProvider + sendersNonce map[cciptypes.ChainSelector]map[string]uint64 // Config maxReportSizeBytes uint64 @@ -72,9 +76,14 @@ type execReportBuilder struct { // State accumulated validationMetadata + // expectedNonce is used to track nonces for multiple messages from the same sender. + expectedNonce map[cciptypes.ChainSelector]map[string]uint64 // Result execReports []cciptypes.ExecutePluginReportSingleChain + + // TODO: remove temporary feature flagging + nonceCheckingEnabled bool // defaults to disabled for backwards compatibility. } func (b *execReportBuilder) Add( diff --git a/execute/report/report.go b/execute/report/report.go index 36e10bb47..cd26ae211 100644 --- a/execute/report/report.go +++ b/execute/report/report.go @@ -38,6 +38,12 @@ func buildSingleChainReportHelper( } } + if len(readyMessages) == 0 { + lggr.Infow("no messages ready for execution", + "sourceChain", report.SourceChain) + return cciptypes.ExecutePluginReportSingleChain{}, nil + } + numMsg := len(report.Messages) if len(report.TokenData) != numMsg { return cciptypes.ExecutePluginReportSingleChain{}, @@ -124,12 +130,13 @@ const ( TokenDataNotReady messageStatus = "token_data_not_ready" //nolint:gosec // this is not a password TokenDataFetchError messageStatus = "token_data_fetch_error" InsufficientRemainingBatchGas messageStatus = "insufficient_remaining_batch_gas" + MissingNoncesForChain messageStatus = "missing_nonces_for_chain" + MissingNonce messageStatus = "missing_nonce" + InvalidNonce messageStatus = "invalid_nonce" /* SenderAlreadySkipped messageStatus = "sender_already_skipped" MessageMaxGasCalcError messageStatus = "message_max_gas_calc_error" InsufficientRemainingBatchDataLength messageStatus = "insufficient_remaining_batch_data_length" - MissingNonce messageStatus = "missing_nonce" - InvalidNonce messageStatus = "invalid_nonce" AggregateTokenValueComputeError messageStatus = "aggregate_token_value_compute_error" AggregateTokenLimitExceeded messageStatus = "aggregate_token_limit_exceeded" TokenNotInDestTokenPrices messageStatus = "token_not_in_dest_token_prices" @@ -150,6 +157,8 @@ func (b *execReportBuilder) checkMessage( ctx context.Context, idx int, execReport exectypes.CommitData, // TODO: get rid of the nolint when the error is used ) (exectypes.CommitData, messageStatus, error) { // nolint this will use the error eventually + result := execReport + if idx >= len(execReport.Messages) { b.lggr.Errorw("message index out of range", "index", idx, "numMessages", len(execReport.Messages)) return execReport, Unknown, fmt.Errorf("message index out of range") @@ -157,7 +166,7 @@ func (b *execReportBuilder) checkMessage( msg := execReport.Messages[idx] - // Check if the message has already been executed. + // 1. Check if the message has already been executed. if slices.Contains(execReport.ExecutedMessages, msg.Header.SequenceNumber) { b.lggr.Infow( "message already executed", @@ -167,7 +176,7 @@ func (b *execReportBuilder) checkMessage( return execReport, AlreadyExecuted, nil } - // Check if token data is ready. + // 2. Check if token data is ready. if b.tokenDataReader == nil { return execReport, Unknown, fmt.Errorf("token data reader must be initialized") } @@ -182,7 +191,7 @@ func (b *execReportBuilder) checkMessage( "error", err) return execReport, TokenDataNotReady, nil } - b.lggr.Infow( + b.lggr.Errorw( "unable to read token data - unknown error", "messageID", msg.Header.MessageID, "sourceChain", execReport.SourceChain, @@ -191,8 +200,8 @@ func (b *execReportBuilder) checkMessage( return execReport, TokenDataFetchError, nil } - execReport.TokenData = padSlice(execReport.TokenData, idx+1, nil) - execReport.TokenData[idx] = tokenData + result.TokenData = padSlice(execReport.TokenData, idx+1, nil) + result.TokenData[idx] = tokenData b.lggr.Infow( "read token data", "messageID", msg.Header.MessageID, @@ -200,10 +209,59 @@ func (b *execReportBuilder) checkMessage( "seqNum", msg.Header.SequenceNumber, "data", tokenData) - // TODO: Check for valid nonce + // 3. Check if the message has a valid nonce. + if msg.Header.Nonce != 0 && b.nonceCheckingEnabled { + // Sequenced messages have non-zero nonces. + + if _, ok := b.sendersNonce[execReport.SourceChain]; !ok { + b.lggr.Errorw("Skipping message - nonces not available for chain", + "messageID", msg.Header.MessageID, + "sourceChain", execReport.SourceChain, + "seqNum", msg.Header.SequenceNumber, + ) + return execReport, MissingNoncesForChain, nil + } + + chainNonces := b.sendersNonce[execReport.SourceChain] + sender := msg.Sender.String() + if _, ok := chainNonces[sender]; !ok { + b.lggr.Errorw("Skipping message - missing nonce", + "messageID", msg.Header.MessageID, + "sourceChain", execReport.SourceChain, + "seqNum", msg.Header.SequenceNumber, + ) + return execReport, MissingNonce, nil + } + + if b.expectedNonce == nil { + // initialize expected nonce if needed. + b.expectedNonce = make(map[cciptypes.ChainSelector]map[string]uint64) + } + if _, ok := b.expectedNonce[execReport.SourceChain]; !ok { + // initialize expected nonce if needed. + b.expectedNonce[execReport.SourceChain] = make(map[string]uint64) + } + if _, ok := b.expectedNonce[execReport.SourceChain][sender]; !ok { + b.expectedNonce[execReport.SourceChain][sender] = chainNonces[sender] + 1 + } + + // Check expected nonce is valid for sequenced messages. + if msg.Header.Nonce != b.expectedNonce[execReport.SourceChain][sender] { + b.lggr.Warnw("Skipping message - invalid nonce", + "messageID", msg.Header.MessageID, + "sourceChain", execReport.SourceChain, + "seqNum", msg.Header.SequenceNumber, + "have", msg.Header.Nonce, + "want", b.expectedNonce[execReport.SourceChain][sender], + ) + return execReport, InvalidNonce, nil + } + b.expectedNonce[execReport.SourceChain][sender] = b.expectedNonce[execReport.SourceChain][sender] + 1 + } + // TODO: Check for fee boost - return execReport, ReadyToExecute, nil + return result, ReadyToExecute, nil } func (b *execReportBuilder) verifyReport( @@ -284,6 +342,10 @@ func (b *execReportBuilder) buildSingleChainReport( } } + if len(readyMessages) == 0 { + return cciptypes.ExecutePluginReportSingleChain{}, report, ErrEmptyReport + } + // Attempt to include all messages in the report. finalReport, err := buildSingleChainReportHelper(b.ctx, b.lggr, b.hasher, report, readyMessages) diff --git a/execute/report/report_test.go b/execute/report/report_test.go index 2c1d59fee..d9c04cf8d 100644 --- a/execute/report/report_test.go +++ b/execute/report/report_test.go @@ -2,6 +2,7 @@ package report import ( "context" + crand "crypto/rand" "fmt" "math/rand" "reflect" @@ -25,6 +26,12 @@ import ( "github.com/smartcontractkit/chainlink-ccip/internal/mocks" ) +func randomAddress() string { + b := make([]byte, 20) + _, _ = crand.Read(b) // Assignment for errcheck. Only used in tests so we can ignore. + return cciptypes.Bytes(b).String() +} + // mustMakeBytes parses a given string into a byte array, any error causes a panic. Pass in an empty string for a // random byte array. // nolint:unparam // surly this will be useful at some point... @@ -169,10 +176,37 @@ func makeMessage(src cciptypes.ChainSelector, num cciptypes.SeqNum, nonce uint64 SourceChainSelector: src, SequenceNumber: num, MsgHash: cciptypes.Bytes32{}, + Nonce: nonce, }, } } +// makeTestCommitReportWithSenders is the same as makeTestCommitReport but overrides the senders. +func makeTestCommitReportWithSenders( + hasher cciptypes.MessageHasher, + numMessages, + srcChain, + firstSeqNum, + block int, + timestamp int64, + senders []cciptypes.Bytes, + rootOverride cciptypes.Bytes32, + executed []cciptypes.SeqNum, +) exectypes.CommitData { + if len(senders) == 0 || len(senders) != numMessages { + panic("wrong number of senders provided") + } + + data := makeTestCommitReport(hasher, numMessages, srcChain, firstSeqNum, + block, timestamp, senders[0], rootOverride, executed) + + for i := range data.Messages { + data.Messages[i].Sender = senders[i] + } + + return data +} + // makeTestCommitReport creates a basic commit report with messages given different parameters. This function // will panic if the input parameters are inconsistent. func makeTestCommitReport( @@ -182,6 +216,7 @@ func makeTestCommitReport( firstSeqNum, block int, timestamp int64, + sender cciptypes.Bytes, rootOverride cciptypes.Bytes32, executed []cciptypes.SeqNum, ) exectypes.CommitData { @@ -195,10 +230,12 @@ func makeTestCommitReport( } var messages []cciptypes.Message for i := 0; i < numMessages; i++ { - messages = append(messages, makeMessage( + msg := makeMessage( cciptypes.ChainSelector(srcChain), cciptypes.SeqNum(i+firstSeqNum), - uint64(i))) + uint64(i)+1) + msg.Sender = sender + messages = append(messages, msg) } commitReport := exectypes.CommitData{ @@ -263,10 +300,14 @@ func Test_buildSingleChainReport_Errors(t *testing.T) { }{ { name: "token data mismatch", - wantErr: "token data length mismatch: got 2, expected 0", + wantErr: "token data length mismatch: got 2, expected 1", args: args{ report: exectypes.CommitData{ - TokenData: make([][][]byte, 2), + TokenData: make([][][]byte, 2), + SequenceNumberRange: cciptypes.NewSeqNumRange(cciptypes.SeqNum(100), cciptypes.SeqNum(100)), + Messages: []cciptypes.Message{ + {Header: cciptypes.RampMessageHeader{}}, + }, }, }, }, @@ -384,9 +425,25 @@ func Test_Builder_Build(t *testing.T) { codec := mocks.NewExecutePluginJSONReportCodec() lggr := logger.Test(t) tokenDataReader := tdr{mode: good} + sender, err := cciptypes.NewBytesFromString(randomAddress()) + require.NoError(t, err) + defaultNonces := map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + sender.String(): 0, + }, + 2: { + sender.String(): 0, + }, + } + tenSenders := make([]cciptypes.Bytes, 10) + for i := range tenSenders { + tenSenders[i], err = cciptypes.NewBytesFromString(randomAddress()) + require.NoError(t, err) + } type args struct { reports []exectypes.CommitData + nonces map[cciptypes.ChainSelector]map[string]uint64 maxReportSize uint64 maxGasLimit uint64 } @@ -410,10 +467,12 @@ func Test_Builder_Build(t *testing.T) { { name: "half report", args: args{ - maxReportSize: 2300, + maxReportSize: 2529, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), }, @@ -428,8 +487,10 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 10000, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), }, @@ -443,11 +504,14 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 15000, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), makeTestCommitReport(hasher, 20, 2, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), }, @@ -459,13 +523,16 @@ func Test_Builder_Build(t *testing.T) { { name: "one and half reports", args: args{ - maxReportSize: 8500, + maxReportSize: 9500, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), makeTestCommitReport(hasher, 20, 2, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), }, @@ -478,13 +545,16 @@ func Test_Builder_Build(t *testing.T) { { name: "exactly one report", args: args{ - maxReportSize: 4200, + maxReportSize: 4600, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), makeTestCommitReport(hasher, 20, 2, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil), }, @@ -497,10 +567,16 @@ func Test_Builder_Build(t *testing.T) { { name: "execute remainder of partially executed report", args: args{ - maxReportSize: 2500, + maxReportSize: 2600, maxGasLimit: 10000000, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + sender.String(): 5, + }, + }, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. []cciptypes.SeqNum{100, 101, 102, 103, 104}), }, @@ -512,10 +588,16 @@ func Test_Builder_Build(t *testing.T) { { name: "partially execute remainder of partially executed report", args: args{ - maxReportSize: 2050, + maxReportSize: 2200, maxGasLimit: 10000000, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + sender.String(): 5, + }, + }, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. []cciptypes.SeqNum{100, 101, 102, 103, 104}), }, @@ -530,8 +612,18 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 3500, maxGasLimit: 10000000, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + tenSenders[1].String(): 1, + tenSenders[3].String(): 3, + tenSenders[5].String(): 5, + tenSenders[7].String(): 7, + tenSenders[9].String(): 9, + }, + }, reports: []exectypes.CommitData{ - makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + makeTestCommitReportWithSenders(hasher, 10, 1, 100, 999, 10101010101, + tenSenders, cciptypes.Bytes32{}, // generate a correct root. []cciptypes.SeqNum{100, 102, 104, 106, 108}), }, @@ -543,10 +635,20 @@ func Test_Builder_Build(t *testing.T) { { name: "partially execute remainder of partially executed sparse report", args: args{ - maxReportSize: 2050, + maxReportSize: 2250, maxGasLimit: 10000000, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + tenSenders[1].String(): 1, + tenSenders[3].String(): 3, + tenSenders[5].String(): 5, + tenSenders[7].String(): 7, + tenSenders[9].String(): 9, + }, + }, reports: []exectypes.CommitData{ - makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + makeTestCommitReportWithSenders(hasher, 10, 1, 100, 999, 10101010101, + tenSenders, cciptypes.Bytes32{}, // generate a correct root. []cciptypes.SeqNum{100, 102, 104, 106, 108}), }, @@ -561,8 +663,10 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 10000, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ breakCommitReport(makeTestCommitReport(hasher, 10, 1, 101, 1000, 10101010102, + sender, cciptypes.Bytes32{}, // generate a correct root. nil)), }, @@ -572,8 +676,10 @@ func Test_Builder_Build(t *testing.T) { { name: "invalid merkle root", args: args{ + nonces: defaultNonces, reports: []exectypes.CommitData{ makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, mustMakeBytes(""), // random root nil), }, @@ -585,9 +691,11 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 10000, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ setMessageData(5, 20000, makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil)), }, @@ -602,10 +710,12 @@ func Test_Builder_Build(t *testing.T) { args: args{ maxReportSize: 10000, maxGasLimit: 10000000, + nonces: defaultNonces, reports: []exectypes.CommitData{ setMessageData(8, 20000, setMessageData(5, 20000, makeTestCommitReport(hasher, 10, 1, 100, 999, 10101010101, + sender, cciptypes.Bytes32{}, // generate a correct root. nil))), }, @@ -631,6 +741,7 @@ func Test_Builder_Build(t *testing.T) { tokenDataReader, codec, evm.EstimateProvider{}, + tt.args.nonces, tt.args.maxReportSize, tt.args.maxGasLimit) var updatedMessages []exectypes.CommitData @@ -815,13 +926,14 @@ func Test_execReportBuilder_verifyReport(t *testing.T) { } b := &execReportBuilder{ - ctx: context.Background(), - lggr: lggr, - encoder: resolvedEncoder, - estimateProvider: tt.fields.estimateProvider, - maxReportSizeBytes: tt.fields.maxReportSizeBytes, - maxGas: tt.fields.maxGas, - accumulated: tt.fields.accumulated, + nonceCheckingEnabled: true, // TODO: remove feature flag. + ctx: context.Background(), + lggr: lggr, + encoder: resolvedEncoder, + estimateProvider: tt.fields.estimateProvider, + maxReportSizeBytes: tt.fields.maxReportSizeBytes, + maxGas: tt.fields.maxGas, + accumulated: tt.fields.accumulated, } isValid, metadata, err := b.verifyReport(context.Background(), tt.args.execReport) if tt.expectedError != "" { @@ -880,6 +992,7 @@ func Test_execReportBuilder_checkMessage(t *testing.T) { } type args struct { idx int + nonces map[cciptypes.ChainSelector]map[string]uint64 execReport exectypes.CommitData } tests := []struct { @@ -1010,6 +1123,62 @@ func Test_execReportBuilder_checkMessage(t *testing.T) { TokenData: [][][]byte{nil, nil}, }, }, + { + name: "missing chain nonce", + args: args{ + idx: 0, + execReport: exectypes.CommitData{ + SourceChain: 1, + Messages: []cciptypes.Message{ + makeMessage(1, 100, 1), + }, + }, + }, + fields: fields{ + tokenDataReader: tdr{mode: noop}, + }, + expectedStatus: MissingNoncesForChain, + }, + { + name: "missing sender nonce", + args: args{ + idx: 0, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: {}, + }, + execReport: exectypes.CommitData{ + SourceChain: 1, + Messages: []cciptypes.Message{ + makeMessage(1, 100, 1), + }, + }, + }, + fields: fields{ + tokenDataReader: tdr{mode: noop}, + }, + expectedStatus: MissingNonce, + }, + { + name: "invalid sender nonce", + args: args{ + idx: 0, + nonces: map[cciptypes.ChainSelector]map[string]uint64{ + 1: { + "0x": 99, + }, + }, + execReport: exectypes.CommitData{ + SourceChain: 1, + Messages: []cciptypes.Message{ + makeMessage(1, 100, 1), + }, + }, + }, + fields: fields{ + tokenDataReader: tdr{mode: noop}, + }, + expectedStatus: InvalidNonce, + }, } for _, tt := range tests { tt := tt @@ -1026,10 +1195,12 @@ func Test_execReportBuilder_checkMessage(t *testing.T) { } b := &execReportBuilder{ - lggr: lggr, - tokenDataReader: resolvedTokenDataReader, - estimateProvider: evm.EstimateProvider{}, - accumulated: tt.fields.accumulated, + nonceCheckingEnabled: true, // TODO: remove feature flag. + lggr: lggr, + tokenDataReader: resolvedTokenDataReader, + estimateProvider: evm.EstimateProvider{}, + accumulated: tt.fields.accumulated, + sendersNonce: tt.args.nonces, } data, status, err := b.checkMessage(context.Background(), tt.args.idx, tt.args.execReport) if tt.expectedError != "" { diff --git a/go.mod b/go.mod index d4c9afa18..41855fdbc 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.22.5 require ( github.com/deckarep/golang-set/v2 v2.6.0 - github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712162033-89bd3351ce6e - github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 + github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 + github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c github.com/stretchr/testify v1.9.0 go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.24.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/sync v0.7.0 google.golang.org/grpc v1.64.1 @@ -20,27 +20,28 @@ require ( github.com/buger/jsonparser v1.1.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/invopop/jsonschema v0.12.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mr-tron/base58 v1.2.0 // indirect github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.17.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.45.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect + github.com/prometheus/common v0.44.0 // indirect + github.com/prometheus/procfs v0.11.1 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect - github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 // indirect + github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect + golang.org/x/net v0.26.0 // indirect + golang.org/x/sys v0.21.0 // indirect golang.org/x/text v0.16.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240520151616-dc85e6b867a5 // indirect google.golang.org/protobuf v1.34.1 // indirect diff --git a/go.sum b/go.sum index 07ffbc2bd..4aa10e4da 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,9 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -25,8 +28,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= -github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= +github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= +github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= github.com/nolag/mapstructure v1.5.2-0.20240625151721-90ea83a3f479 h1:1jCGDLFXDOHF2sdeTJYKrIuSLGMpQZpgXXHNGXR5Ouk= @@ -40,22 +43,22 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM= -github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM= +github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= +github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= +github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= +github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI= +github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= -github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= +github.com/santhosh-tekuri/jsonschema/v5 v5.2.0 h1:WCcC4vZDS1tYNxjWlwRJZQy28r8CMoggKnxNzxsVDMQ= +github.com/santhosh-tekuri/jsonschema/v5 v5.2.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712162033-89bd3351ce6e h1:vKVNJfFXy4Wdq5paOV0/fNgql2GoXkei10+D+SmC+Qs= -github.com/smartcontractkit/chainlink-common v0.1.7-0.20240712162033-89bd3351ce6e/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= -github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7 h1:e38V5FYE7DA1JfKXeD5Buo/7lczALuVXlJ8YNTAUxcw= -github.com/smartcontractkit/libocr v0.0.0-20240717100443-f6226e09bee7/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= +github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834 h1:pTf4xdcmiWBqWZ6rTy2RMTDBzhHk89VC1pM7jXKQztI= +github.com/smartcontractkit/chainlink-common v0.2.1-0.20240717132349-ee5af9b79834/go.mod h1:fh9eBbrReCmv31bfz52ENCAMa7nTKQbdhb2B3+S2VGo= +github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c h1:lIyMbTaF2H0Q71vkwZHX/Ew4KF2BxiKhqEXwF8rn+KI= +github.com/smartcontractkit/libocr v0.0.0-20240419185742-fd3cab206b2c/go.mod h1:fb1ZDVXACvu4frX3APHZaEBp0xi1DIm34DcA0CwTsZM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -74,16 +77,17 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= gonum.org/v1/gonum v0.14.0 h1:2NiG67LD1tEH0D7kM+ps2V+fXmsAnpUeec7n8tcr4S0= diff --git a/internal/mocks/ccipreader.go b/internal/mocks/ccipreader.go index 5a8819120..ff1777069 100644 --- a/internal/mocks/ccipreader.go +++ b/internal/mocks/ccipreader.go @@ -48,6 +48,15 @@ func (r CCIPReader) NextSeqNum(ctx context.Context, chains []cciptypes.ChainSele return args.Get(0).([]cciptypes.SeqNum), args.Error(1) } +func (r CCIPReader) Nonces( + ctx context.Context, + source, dest cciptypes.ChainSelector, + addresses []string, +) (map[string]uint64, error) { + args := r.Called(ctx, source, dest, addresses) + return args.Get(0).(map[string]uint64), args.Error(1) +} + func (r CCIPReader) GasPrices(ctx context.Context, chains []cciptypes.ChainSelector) ([]cciptypes.BigInt, error) { args := r.Called(ctx, chains) return args.Get(0).([]cciptypes.BigInt), args.Error(1) diff --git a/internal/mocks/inmem/ccipreader_inmem.go b/internal/mocks/inmem/ccipreader_inmem.go index 1680988b4..3f62e0d29 100644 --- a/internal/mocks/inmem/ccipreader_inmem.go +++ b/internal/mocks/inmem/ccipreader_inmem.go @@ -97,6 +97,14 @@ func (r InMemoryCCIPReader) NextSeqNum( panic("implement me") } +func (r InMemoryCCIPReader) Nonces( + ctx context.Context, + source, dest cciptypes.ChainSelector, + addresses []string, +) (map[string]uint64, error) { + panic("implement me") +} + func (r InMemoryCCIPReader) GasPrices( ctx context.Context, chains []cciptypes.ChainSelector, ) ([]cciptypes.BigInt, error) { diff --git a/internal/reader/ccip.go b/internal/reader/ccip.go index e877e86e5..14136411b 100644 --- a/internal/reader/ccip.go +++ b/internal/reader/ccip.go @@ -9,9 +9,10 @@ import ( "sync" "time" - types2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" "golang.org/x/sync/errgroup" + types2 "github.com/smartcontractkit/libocr/offchainreporting2plus/types" + "github.com/smartcontractkit/chainlink-common/pkg/logger" "github.com/smartcontractkit/chainlink-common/pkg/types" cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccipocr3" @@ -57,6 +58,13 @@ type CCIP interface { // TODO: if destination was a parameter, this could be a capability reused across plugin instances. NextSeqNum(ctx context.Context, chains []cciptypes.ChainSelector) (seqNum []cciptypes.SeqNum, err error) + // Nonces fetches all nonces for the provided selector/address pairs. Addresses are the hex encoded raw address. + Nonces( + ctx context.Context, + source, dest cciptypes.ChainSelector, + addresses []string, + ) (map[string]uint64, error) + // GasPrices reads the provided chains gas prices. GasPrices(ctx context.Context, chains []cciptypes.ChainSelector) ([]cciptypes.BigInt, error) @@ -401,6 +409,14 @@ func (r *CCIPChainReader) NextSeqNum( return res, err } +func (r *CCIPChainReader) Nonces( + ctx context.Context, + source, dest cciptypes.ChainSelector, + addresses []string, +) (map[string]uint64, error) { + return nil, fmt.Errorf("implement me") +} + func (r *CCIPChainReader) GasPrices(ctx context.Context, chains []cciptypes.ChainSelector) ([]cciptypes.BigInt, error) { if err := r.validateWriterExistence(chains...); err != nil { return nil, err