diff --git a/deployment/ccip/changeset/add_chain_test.go b/deployment/ccip/changeset/add_chain_test.go index 2d79a76005d..defeef074ca 100644 --- a/deployment/ccip/changeset/add_chain_test.go +++ b/deployment/ccip/changeset/add_chain_test.go @@ -225,10 +225,10 @@ func TestAddChainInbound(t *testing.T) { ExtraArgs: nil, }) require.NoError(t, - ccipdeployment.ConfirmCommitWithExpectedSeqNumRange(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, cciptypes.SeqNumRange{ + commonutils.JustError(ccipdeployment.ConfirmCommitWithExpectedSeqNumRange(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, cciptypes.SeqNumRange{ cciptypes.SeqNum(1), cciptypes.SeqNum(msgSentEvent.SequenceNumber), - })) + }))) require.NoError(t, commonutils.JustError(ccipdeployment.ConfirmExecWithSeqNr(t, e.Env.Chains[initialDeploy[0]], e.Env.Chains[newChain], state.Chains[newChain].OffRamp, &startBlock, msgSentEvent.SequenceNumber))) diff --git a/deployment/ccip/test_assertions.go b/deployment/ccip/test_assertions.go index 0c15c8b95ed..1f0e407b23e 100644 --- a/deployment/ccip/test_assertions.go +++ b/deployment/ccip/test_assertions.go @@ -14,6 +14,7 @@ import ( "golang.org/x/sync/errgroup" "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" + commonutils "github.com/smartcontractkit/chainlink-common/pkg/utils" "github.com/smartcontractkit/chainlink-common/pkg/utils/tests" "github.com/smartcontractkit/chainlink/deployment/environment/memory" @@ -176,7 +177,7 @@ func ConfirmCommitForAllWithExpectedSeqNums( return nil } - return ConfirmCommitWithExpectedSeqNumRange( + return commonutils.JustError(ConfirmCommitWithExpectedSeqNumRange( t, srcChain, dstChain, @@ -185,7 +186,7 @@ func ConfirmCommitForAllWithExpectedSeqNums( ccipocr3.SeqNumRange{ ccipocr3.SeqNum(expectedSeqNums[dstChain.Selector]), ccipocr3.SeqNum(expectedSeqNums[dstChain.Selector]), - }) + })) }) } } @@ -220,14 +221,14 @@ func ConfirmCommitWithExpectedSeqNumRange( offRamp *offramp.OffRamp, startBlock *uint64, expectedSeqNumRange ccipocr3.SeqNumRange, -) error { +) (*offramp.OffRampCommitReportAccepted, error) { sink := make(chan *offramp.OffRampCommitReportAccepted) subscription, err := offRamp.WatchCommitReportAccepted(&bind.WatchOpts{ Context: context.Background(), Start: startBlock, }, sink) if err != nil { - return fmt.Errorf("error to subscribe CommitReportAccepted : %w", err) + return nil, fmt.Errorf("error to subscribe CommitReportAccepted : %w", err) } defer subscription.Unsubscribe() @@ -268,17 +269,17 @@ func ConfirmCommitWithExpectedSeqNumRange( if mr.SourceChainSelector == src.Selector && uint64(expectedSeqNumRange.Start()) >= mr.MinSeqNr && uint64(expectedSeqNumRange.End()) <= mr.MaxSeqNr { - t.Logf("Received commit report for [%d, %d] on selector %d from source selector %d expected seq nr range %s, token prices: %v", - mr.MinSeqNr, mr.MaxSeqNr, dest.Selector, src.Selector, expectedSeqNumRange.String(), event.PriceUpdates.TokenPriceUpdates) - return nil + t.Logf("Received commit report for [%d, %d] on selector %d from source selector %d expected seq nr range %s, token prices: %v, tx hash: %s", + mr.MinSeqNr, mr.MaxSeqNr, dest.Selector, src.Selector, expectedSeqNumRange.String(), event.PriceUpdates.TokenPriceUpdates, event.Raw.TxHash.String()) + return event, nil } } } } case subErr := <-subscription.Err(): - return fmt.Errorf("subscription error: %w", subErr) + return nil, fmt.Errorf("subscription error: %w", subErr) case <-timer.C: - return fmt.Errorf("timed out after waiting %s duration for commit report on chain selector %d from source selector %d expected seq nr range %s", + return nil, fmt.Errorf("timed out after waiting %s duration for commit report on chain selector %d from source selector %d expected seq nr range %s", duration.String(), dest.Selector, src.Selector, expectedSeqNumRange.String()) case report := <-sink: if len(report.MerkleRoots) > 0 { @@ -290,7 +291,7 @@ func ConfirmCommitWithExpectedSeqNumRange( uint64(expectedSeqNumRange.End()) <= mr.MaxSeqNr { t.Logf("Received commit report for [%d, %d] on selector %d from source selector %d expected seq nr range %s, token prices: %v", mr.MinSeqNr, mr.MaxSeqNr, dest.Selector, src.Selector, expectedSeqNumRange.String(), report.PriceUpdates.TokenPriceUpdates) - return nil + return report, nil } } } diff --git a/deployment/ccip/test_helpers.go b/deployment/ccip/test_helpers.go index a8a570bed2d..8c90ce70be1 100644 --- a/deployment/ccip/test_helpers.go +++ b/deployment/ccip/test_helpers.go @@ -523,10 +523,10 @@ func ConfirmRequestOnSourceAndDest(t *testing.T, env deployment.Environment, sta fmt.Printf("Request sent for seqnr %d", msgSentEvent.SequenceNumber) require.NoError(t, - ConfirmCommitWithExpectedSeqNumRange(t, env.Chains[sourceCS], env.Chains[destCS], state.Chains[destCS].OffRamp, &startBlock, cciptypes.SeqNumRange{ + commonutils.JustError(ConfirmCommitWithExpectedSeqNumRange(t, env.Chains[sourceCS], env.Chains[destCS], state.Chains[destCS].OffRamp, &startBlock, cciptypes.SeqNumRange{ cciptypes.SeqNum(msgSentEvent.SequenceNumber), cciptypes.SeqNum(msgSentEvent.SequenceNumber), - })) + }))) fmt.Printf("Commit confirmed for seqnr %d", msgSentEvent.SequenceNumber) require.NoError( diff --git a/integration-tests/smoke/ccip_batching_test.go b/integration-tests/smoke/ccip_batching_test.go index a029da6ffcc..b12615abb34 100644 --- a/integration-tests/smoke/ccip_batching_test.go +++ b/integration-tests/smoke/ccip_batching_test.go @@ -1,6 +1,10 @@ package smoke import ( + "context" + "fmt" + "math/big" + "sync" "testing" "github.com/ethereum/go-ethereum/accounts/abi/bind" @@ -8,12 +12,15 @@ import ( "github.com/stretchr/testify/require" "golang.org/x/exp/maps" + "github.com/smartcontractkit/chainlink-ccip/pkg/types/ccipocr3" jobv1 "github.com/smartcontractkit/chainlink-protos/job-distributor/v1/job" "github.com/smartcontractkit/chainlink/deployment" ccdeploy "github.com/smartcontractkit/chainlink/deployment/ccip" "github.com/smartcontractkit/chainlink/deployment/ccip/changeset" "github.com/smartcontractkit/chainlink/integration-tests/ccip-tests/testsetups" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/multicall3" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/offramp" + "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/onramp" "github.com/smartcontractkit/chainlink/v2/core/gethwrappers/ccip/generated/router" "github.com/smartcontractkit/chainlink/v2/core/logger" ) @@ -83,80 +90,173 @@ func Test_CCIPBatching(t *testing.T) { require.NoError(t, ccdeploy.AddLaneWithDefaultPrices(e.Env, state, sourceChain1, destChain)) require.NoError(t, ccdeploy.AddLaneWithDefaultPrices(e.Env, state, sourceChain2, destChain)) - // var ( - // replayed bool - // nonce map[uint64]uint64 - // senderSource1 = common.LeftPadBytes(e.Env.Chains[sourceChain1].DeployerKey.From.Bytes(), 32) - // senderSource2 = common.LeftPadBytes(e.Env.Chains[sourceChain2].DeployerKey.From.Bytes(), 32) - // outSource1 messagingTestCaseOutput - // outSource2 messagingTestCaseOutput - // setupSource1 = testCaseSetup{ - // t: t, - // sender: senderSource1, - // deployedEnv: e, - // onchainState: state, - // sourceChain: sourceChain1, - // destChain: destChain, - // } - // setupSource2 = testCaseSetup{ - // t: t, - // sender: senderSource2, - // deployedEnv: e, - // onchainState: state, - // sourceChain: sourceChain2, - // destChain: destChain, - // } - // ) + const ( + numMessages = 5 + ) t.Run("batch data only messages from multiple sources", func(t *testing.T) { + var wg sync.WaitGroup + + wg.Add(1) + go func(sourceChainSelector uint64) { + defer wg.Done() + sendMessages( + ctx, + t, + e.Env.Chains[sourceChainSelector], + e.Env.Chains[sourceChainSelector].DeployerKey, + state.Chains[sourceChainSelector].OnRamp, + state.Chains[sourceChainSelector].Router, + state.Chains[sourceChainSelector].Multicall3, + destChain, + numMessages, + common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + ) + }(sourceChain1) + + wg.Add(1) + go func(sourceChainSelector uint64) { + defer wg.Done() + sendMessages( + ctx, + t, + e.Env.Chains[sourceChainSelector], + e.Env.Chains[sourceChainSelector].DeployerKey, + state.Chains[sourceChainSelector].OnRamp, + state.Chains[sourceChainSelector].Router, + state.Chains[sourceChainSelector].Multicall3, + destChain, + numMessages, + common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), + ) + }(sourceChain2) + + wg.Wait() + + // confirm the commit reports + var ( + sourceChain1Report *offramp.OffRampCommitReportAccepted + sourceChain2Report *offramp.OffRampCommitReportAccepted + ) + wg.Add(1) + go func() { + defer wg.Done() + var err error + sourceChain1Report, err = ccdeploy.ConfirmCommitWithExpectedSeqNumRange(t, + e.Env.Chains[sourceChain1], + e.Env.Chains[destChain], + state.Chains[destChain].OffRamp, + nil, + ccipocr3.NewSeqNumRange(ccipocr3.SeqNum(1), ccipocr3.SeqNum(numMessages)), + ) + require.NoErrorf(t, err, "failed to confirm commit from chain %d", sourceChain1) + }() + + wg.Add(1) + go func() { + defer wg.Done() + var err error + sourceChain2Report, err = ccdeploy.ConfirmCommitWithExpectedSeqNumRange(t, + e.Env.Chains[sourceChain2], + e.Env.Chains[destChain], + state.Chains[destChain].OffRamp, + nil, + ccipocr3.NewSeqNumRange(ccipocr3.SeqNum(1), ccipocr3.SeqNum(numMessages)), + ) + require.NoErrorf(t, err, "failed to confirm commit from chain %d", sourceChain2) + }() + + t.Log("waiting for commit report") + wg.Wait() + + // the reports should be the same for both, since both roots should be batched within + // that one report. + require.Equal(t, sourceChain1Report, sourceChain2Report, "commit reports should be the same") + }) + + t.Run("batch mix of data only messages and token messages from multiple sources", func(t *testing.T) { // Generate some messages, on each source. // Send them from a multicall contract, i.e multiple ccip messages in the same tx. // assert they are committed in the same batch. + }) +} + +func sendMessages( + ctx context.Context, + t *testing.T, + sourceChain deployment.Chain, + sourceTransactOpts *bind.TransactOpts, + sourceOnRamp *onramp.OnRamp, + sourceRouter *router.Router, + sourceMulticall3 *multicall3.Multicall3, + destChainSelector uint64, + numMessages int, + receiver []byte, +) { + calls, totalValue := genMessages( + ctx, + t, + sourceRouter, + destChainSelector, + numMessages, + receiver, + ) + + // Send the tx with the messages through the multicall + tx, err := sourceMulticall3.Aggregate3Value( + &bind.TransactOpts{ + From: sourceTransactOpts.From, + Signer: sourceTransactOpts.Signer, + Value: totalValue, + }, + calls, + ) + _, err = deployment.ConfirmIfNoError(sourceChain, tx, err) + require.NoError(t, err, "failed to confirm tx") + + // check that the message was emitted + iter, err := sourceOnRamp.FilterCCIPMessageSent( + nil, []uint64{destChainSelector}, nil, + ) + require.NoError(t, err) + + // there should be numMessages messages emitted + for i := 0; i < numMessages; i++ { + require.Truef(t, iter.Next(), "expected %d messages, got %d", numMessages, i+1) + t.Logf("Message id of msg %d: %x", i, iter.Event.Message.Header.MessageId[:]) + } +} + +func genMessages( + ctx context.Context, + t *testing.T, + sourceRouter *router.Router, + destChainSelector uint64, + count int, + receiver []byte, +) (calls []multicall3.Multicall3Call3Value, totalValue *big.Int) { + totalValue = big.NewInt(0) + for i := 0; i < count; i++ { msg := router.ClientEVM2AnyMessage{ - Receiver: common.LeftPadBytes(state.Chains[destChain].Receiver.Address().Bytes(), 32), - Data: []byte("hello world"), + Receiver: receiver, + Data: []byte(fmt.Sprintf("hello world %d", i)), TokenAmounts: nil, FeeToken: common.HexToAddress("0x0"), ExtraArgs: nil, } - fee, err := state.Chains[sourceChain1].Router.GetFee(&bind.CallOpts{ - Context: ctx, - }, destChain, msg) - require.NoError(t, err) - // Send the tx with the message through the multicall - calldata := ccdeploy.CCIPSendCalldata(t, destChain, msg) - tx, err := state.Chains[sourceChain1].Multicall3.Aggregate3Value( - &bind.TransactOpts{ - From: e.Env.Chains[sourceChain1].DeployerKey.From, - Signer: e.Env.Chains[sourceChain1].DeployerKey.Signer, - Value: fee, - }, - []multicall3.Multicall3Call3Value{ - { - Target: state.Chains[sourceChain1].Router.Address(), - AllowFailure: false, - CallData: calldata, - Value: fee, - }, - }, - ) - require.NoError(t, err) - _, err = e.Env.Chains[sourceChain1].Confirm(tx) + fee, err := sourceRouter.GetFee(&bind.CallOpts{Context: ctx}, destChainSelector, msg) require.NoError(t, err) - // check that the message was emitted - iter, err := state.Chains[sourceChain1].OnRamp.FilterCCIPMessageSent( - nil, []uint64{destChain}, nil, - ) - require.NoError(t, err) - require.True(t, iter.Next()) - require.Equal(t, msg.Receiver, iter.Event.Message.Receiver) - }) + totalValue.Add(totalValue, fee) - t.Run("batch mix of data only messages and token messages from multiple sources", func(t *testing.T) { - // Generate some messages, on each source. - // Send them from a multicall contract, i.e multiple ccip messages in the same tx. - // assert they are committed in the same batch. - }) + calls = append(calls, multicall3.Multicall3Call3Value{ + Target: sourceRouter.Address(), + AllowFailure: false, + CallData: ccdeploy.CCIPSendCalldata(t, destChainSelector, msg), + Value: fee, + }) + } + + return calls, totalValue }