From 7cfb6753fad5e3919c301016f757f3f65a48d281 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 14 Aug 2024 10:23:52 -0400 Subject: [PATCH] Move report building into Filter state. Add nonces reader and observation. Add 1.21 to build matrix. Fix tests. Pass nonces to report selection. Add new tests for nonce handling. Fix tests. Fix lint warning. go mod tidy Revert "go mod tidy" This reverts commit c3ca278436b5a7f64a5dcf9bcf1d886dd30f646b. Fix bogus import. Update chainlink-common. Disable nonce checking. Re-enable nonce checking with temporary feature flag. Add additional log message. --- execute/exectypes/observation.go | 12 +- execute/exectypes/outcome.go | 3 +- execute/exectypes/outcome_test.go | 7 +- execute/plugin.go | 119 +++++++++---- execute/plugin_e2e_test.go | 19 +- execute/plugin_test.go | 8 +- execute/report/builder.go | 9 + execute/report/report.go | 80 ++++++++- execute/report/report_test.go | 217 ++++++++++++++++++++--- go.mod | 21 +-- go.sum | 44 ++--- internal/mocks/ccipreader.go | 9 + internal/mocks/inmem/ccipreader_inmem.go | 8 + internal/reader/ccip.go | 18 +- 14 files changed, 458 insertions(+), 116 deletions(-) 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