diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go index 371ddc61e14..572d8e84c07 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp.go @@ -168,10 +168,10 @@ func (o *OnRamp) GetSendRequestsBetweenSeqNums(ctx context.Context, seqNumMin, s return res, nil } -func (o *OnRamp) GetSendRequestsForSeqNums(ctx context.Context, seqNrs []cciptypes.SequenceNumberRange, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { - seqNrRanges := make([]query.Expression, 0, len(seqNrs)) - for _, seqNr := range seqNrs { - seqNrRanges = append(seqNrRanges, query.And( +func (o *OnRamp) GetSendRequestsForSeqNums(ctx context.Context, seqNums []cciptypes.SequenceNumberRange, finalized bool) ([]cciptypes.EVM2EVMMessageWithTxMeta, error) { + seqNumRanges := make([]query.Expression, 0, len(seqNums)) + for _, seqNr := range seqNums { + seqNumRanges = append(seqNumRanges, query.And( logpoller.NewEventByWordFilter(o.sendRequestedEventSig, uint8(o.sendRequestedSeqNumberWord), []primitives.ValueComparator{ {Value: logpoller.EvmWord(seqNr.Min).Hex(), Operator: primitives.Gte}, }), @@ -181,11 +181,19 @@ func (o *OnRamp) GetSendRequestsForSeqNums(ctx context.Context, seqNrs []cciptyp )) } + // TODO Move to chainlink-common, querying layer should cover cases like these + var seqNumsFilter query.Expression + if len(seqNumRanges) == 1 { + seqNumsFilter = seqNumRanges[0] + } else { + seqNumsFilter = query.Or(seqNumRanges...) + } + sendRequestsQuery, err := query.Where( o.address.String(), logpoller.NewAddressFilter(o.address), logpoller.NewEventSigFilter(o.sendRequestedEventSig), - query.Or(seqNrRanges...), + seqNumsFilter, query.Confidence(ccipdata.LogsConfidence(finalized)), ) if err != nil { diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp_test.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp_test.go new file mode 100644 index 00000000000..5f9d0f10853 --- /dev/null +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/onramp_test.go @@ -0,0 +1,147 @@ +package v1_0_0 + +import ( + "testing" + "time" + + cciptypes "github.com/smartcontractkit/chainlink-common/pkg/types/ccip" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" + "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils" + "github.com/smartcontractkit/chainlink/v2/core/internal/testutils/pgtest" + "github.com/smartcontractkit/chainlink/v2/core/logger" +) + +func TestOnRamp_GetSendRequestsForSeqNums(t *testing.T) { + ctx := testutils.Context(t) + chainID := testutils.NewRandomEVMChainID() + orm := logpoller.NewORM(chainID, pgtest.NewSqlxDB(t), logger.TestLogger(t)) + lpOpts := logpoller.Opts{ + PollPeriod: time.Hour, + FinalityDepth: 2, + BackfillBatchSize: 20, + RpcBatchSize: 10, + KeepFinalizedBlocksDepth: 1000, + } + lp := logpoller.NewLogPoller(orm, nil, logger.TestLogger(t), lpOpts) + + onrampAddress := utils.RandomAddress() + inputLogs := []logpoller.Log{ + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 10, 2, 1, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 11, 3, 1, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 12, 5, 1, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 13, 5, 2, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 14, 5, 3, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 15, 8, 1, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, onrampAddress, 16, 9, 1, utils.RandomBytes32()), + CreateCCIPSenRequestedLog(t, chainID, utils.RandomAddress(), 16, 9, 1, utils.RandomBytes32()), + } + require.NoError(t, orm.InsertLogsWithBlock(ctx, inputLogs, logpoller.NewLogPollerBlock(utils.RandomBytes32(), 20, time.Now(), 5))) + + tests := []struct { + name string + seqNums []cciptypes.SequenceNumberRange + expectedLogsSeqNrs []uint64 + finalized bool + }{ + { + name: "no logs are returned", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 1, Max: 9}, + }, + expectedLogsSeqNrs: []uint64{}, + }, + { + name: "all logs are returned", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 16}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 12, 13, 14, 15, 16}, + }, + { + name: "all logs are returned for wider range", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 8, Max: 17}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 12, 13, 14, 15, 16}, + }, + { + name: "some logs are returned for tighter range", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 11, Max: 14}, + }, + expectedLogsSeqNrs: []uint64{11, 12, 13, 14}, + }, + { + name: "multiple smaller ranges", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 11}, + {Min: 13, Max: 14}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 13, 14}, + }, + { + name: "single element ranges", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 10}, + {Min: 14, Max: 14}, + {Min: 15, Max: 15}, + }, + expectedLogsSeqNrs: []uint64{10, 14, 15}, + }, + { + name: "out of order ranges returns logs in proper order", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 14, Max: 14}, + {Min: 10, Max: 11}, + {Min: 15, Max: 16}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 14, 15, 16}, + }, + { + name: "overlapping ranges returns logs only once", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 14}, + {Min: 13, Max: 15}, + {Min: 11, Max: 12}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 12, 13, 14, 15}, + }, + { + name: "only finalized logs are returned", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 16}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 12, 13, 14}, + finalized: true, + }, + { + name: "finalized logs works with ranges", + seqNums: []cciptypes.SequenceNumberRange{ + {Min: 10, Max: 11}, + {Min: 13, Max: 15}, + {Min: 16, Max: 16}, + }, + expectedLogsSeqNrs: []uint64{10, 11, 13, 14}, + finalized: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + onRamp, err1 := NewOnRamp(logger.TestLogger(t), uint64(123), uint64(123), onrampAddress, lp, nil) + require.NoError(t, err1) + + msgs, err1 := onRamp.GetSendRequestsForSeqNums(ctx, tt.seqNums, tt.finalized) + require.NoError(t, err1) + + require.Len(t, msgs, len(tt.expectedLogsSeqNrs)) + for i, msg := range msgs { + assert.Equal(t, tt.expectedLogsSeqNrs[i], msg.SequenceNumber) + } + }) + } +} diff --git a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/test_helpers.go b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/test_helpers.go index ed2d8772cd5..d9dde659ad3 100644 --- a/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/test_helpers.go +++ b/core/services/ocr2/plugins/ccip/internal/ccipdata/v1_0_0/test_helpers.go @@ -16,7 +16,8 @@ import ( "github.com/smartcontractkit/chainlink/v2/core/chains/evm/logpoller" "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils" ubig "github.com/smartcontractkit/chainlink/v2/core/chains/evm/utils/big" - "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_offramp_1_0_0" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/evm_2_evm_onramp_1_0_0" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/price_registry_1_0_0" "github.com/smartcontractkit/chainlink/v2/core/services/ocr2/plugins/ccip/internal/ccipcalc" ) @@ -60,8 +61,49 @@ func ApplyPriceRegistryUpdate(t *testing.T, user *bind.TransactOpts, addr common } } +func CreateCCIPSenRequestedLog(t *testing.T, chainID *big.Int, address common.Address, seqNr uint64, blockNumber int64, logIndex int64, messageID common.Hash) logpoller.Log { + tAbi, err := evm_2_evm_onramp_1_0_0.EVM2EVMOnRampMetaData.GetAbi() + require.NoError(t, err) + eseEvent, ok := tAbi.Events["CCIPSendRequested"] + require.True(t, ok) + + message := evm_2_evm_onramp_1_0_0.InternalEVM2EVMMessage{ + SourceChainSelector: 123, + Sender: utils.RandomAddress(), + Receiver: utils.RandomAddress(), + SequenceNumber: seqNr, + GasLimit: big.NewInt(100), + Strict: false, + Nonce: 1337, + FeeToken: utils.RandomAddress(), + FeeTokenAmount: big.NewInt(1), + Data: []byte{}, + TokenAmounts: []evm_2_evm_onramp_1_0_0.ClientEVMTokenAmount{}, + MessageId: messageID, + } + + logData, err := eseEvent.Inputs.Pack(message) + require.NoError(t, err) + + topic0 := evm_2_evm_onramp_1_0_0.EVM2EVMOnRampCCIPSendRequested{}.Topic() + + return logpoller.Log{ + Topics: [][]byte{ + topic0[:], + }, + Data: logData, + LogIndex: logIndex, + BlockHash: utils.RandomBytes32(), + BlockNumber: blockNumber, + EventSig: topic0, + Address: address, + TxHash: utils.RandomBytes32(), + EvmChainId: ubig.New(chainID), + } +} + func CreateExecutionStateChangeEventLog(t *testing.T, chainID *big.Int, address common.Address, seqNr uint64, blockNumber int64, logIndex int64, messageID common.Hash) logpoller.Log { - tAbi, err := evm_2_evm_offramp.EVM2EVMOffRampMetaData.GetAbi() + tAbi, err := evm_2_evm_offramp_1_0_0.EVM2EVMOffRampMetaData.GetAbi() require.NoError(t, err) eseEvent, ok := tAbi.Events["ExecutionStateChanged"] require.True(t, ok) @@ -71,7 +113,7 @@ func CreateExecutionStateChangeEventLog(t *testing.T, chainID *big.Int, address seqNrBytes := make([]byte, 8) binary.BigEndian.PutUint64(seqNrBytes, seqNr) seqNrTopic := common.BytesToHash(seqNrBytes) - topic0 := evm_2_evm_offramp.EVM2EVMOffRampExecutionStateChanged{}.Topic() + topic0 := evm_2_evm_offramp_1_0_0.EVM2EVMOffRampExecutionStateChanged{}.Topic() return logpoller.Log{ Topics: [][]byte{