Skip to content

Commit

Permalink
feat(rfq-relayer): highly concurrent e2e test (#3042)
Browse files Browse the repository at this point in the history
* WIP: parallel test

* Feat: working serial

* Feat: working concurrent requests

* Cleanup: revert unnecessary changes

* Cleanup: test name

* Cleanup: lint

* Cleanup: lint
  • Loading branch information
dwasse authored Aug 20, 2024
1 parent b1ddc86 commit 4fd9ef1
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions services/rfq/e2e/rfq_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package e2e_test

import (
"fmt"
"math/big"
"sync"
"testing"
"time"

"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/stretchr/testify/suite"
"github.com/synapsecns/sanguine/core"
"github.com/synapsecns/sanguine/core/metrics"
Expand All @@ -25,6 +28,7 @@ import (
"github.com/synapsecns/sanguine/services/rfq/relayer/service"
"github.com/synapsecns/sanguine/services/rfq/testutil"
"github.com/synapsecns/sanguine/services/rfq/util"
"golang.org/x/sync/errgroup"
)

type IntegrationSuite struct {
Expand Down Expand Up @@ -468,3 +472,168 @@ func (i *IntegrationSuite) TestDispute() {
return result.TxHash == fakeHash && result.Status == guarddb.Disputed && result.TransactionID == txID
})
}

//nolint:gocognit,cyclop
func (i *IntegrationSuite) TestConcurrentBridges() {
// start the relayer and guard
go func() {
_ = i.relayer.Start(i.GetTestContext())
}()
go func() {
_ = i.guard.Start(i.GetTestContext())
}()

// load token contracts
const startAmount = 10000
const rfqAmount = 10
opts := i.destBackend.GetTxContext(i.GetTestContext(), nil)
destUSDC, destUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.destBackend)
realStartAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(startAmount), destUSDC.ContractHandle())
i.NoError(err)
realRFQAmount, err := testutil.AdjustAmount(i.GetTestContext(), big.NewInt(rfqAmount), destUSDC.ContractHandle())
i.NoError(err)

// add initial usdc to relayer on destination
tx, err := destUSDCHandle.MintPublic(opts.TransactOpts, i.relayerWallet.Address(), realStartAmount)
i.Nil(err)
i.destBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.destBackend, destUSDC, i.relayerWallet)

// add initial USDC to relayer on origin
optsOrigin := i.originBackend.GetTxContext(i.GetTestContext(), nil)
originUSDC, originUSDCHandle := i.cctpDeployManager.GetMockMintBurnTokenType(i.GetTestContext(), i.originBackend)
tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.relayerWallet.Address(), realStartAmount)
i.Nil(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.originBackend, originUSDC, i.relayerWallet)

// add initial USDC to user on origin
tx, err = originUSDCHandle.MintPublic(optsOrigin.TransactOpts, i.userWallet.Address(), realStartAmount)
i.Nil(err)
i.originBackend.WaitForConfirmation(i.GetTestContext(), tx)
i.Approve(i.originBackend, originUSDC, i.userWallet)

// non decimal adjusted user want amount
// now our friendly user is going to check the quote and send us some USDC on the origin chain.
i.Eventually(func() bool {
// first he's gonna check the quotes.
userAPIClient, err := client.NewAuthenticatedClient(metrics.Get(), i.apiServer, localsigner.NewSigner(i.userWallet.PrivateKey()))
i.NoError(err)

allQuotes, err := userAPIClient.GetAllQuotes(i.GetTestContext())
i.NoError(err)

// let's figure out the amount of usdc we need
for _, quote := range allQuotes {
if common.HexToAddress(quote.DestTokenAddr) == destUSDC.Address() {
destAmountBigInt, _ := new(big.Int).SetString(quote.DestAmount, 10)
if destAmountBigInt.Cmp(realRFQAmount) > 0 {
// we found our quote!
// now we can move on
return true
}
}
}
return false
})

_, originFastBridge := i.manager.GetFastBridge(i.GetTestContext(), i.originBackend)
auth := i.originBackend.GetTxContext(i.GetTestContext(), i.userWallet.AddressPtr())
parser, err := fastbridge.NewParser(originFastBridge.Address())
i.NoError(err)

txIDs := [][32]byte{}
txMux := sync.Mutex{}
sendBridgeReq := func(nonce *big.Int) (*types.Transaction, error) {
txMux.Lock()
auth.TransactOpts.Nonce = nonce
defer txMux.Unlock()
tx, err = originFastBridge.Bridge(auth.TransactOpts, fastbridge.IFastBridgeBridgeParams{
DstChainId: uint32(i.destBackend.GetChainID()),
To: i.userWallet.Address(),
OriginToken: originUSDC.Address(),
SendChainGas: true,
DestToken: destUSDC.Address(),
OriginAmount: realRFQAmount,
DestAmount: new(big.Int).Sub(realRFQAmount, big.NewInt(5_000_000)),
Deadline: new(big.Int).SetInt64(time.Now().Add(time.Hour * 24).Unix()),
})
if err != nil {
return nil, fmt.Errorf("failed to send bridge request: %w", err)
}
return tx, nil
}

// send several txs at once and record txids
numTxs := 100
txIDMux := sync.Mutex{}
g, ctx := errgroup.WithContext(i.GetTestContext())
for k := 0; k < numTxs; k++ {
nonce := big.NewInt(int64(k))
g.Go(func() error {
tx, err := sendBridgeReq(nonce)
if err != nil {
return fmt.Errorf("failed to send bridge request: %w", err)
}

i.originBackend.WaitForConfirmation(ctx, tx)
receipt, err := i.originBackend.TransactionReceipt(ctx, tx.Hash())
if err != nil {
return fmt.Errorf("failed to get receipt: %w", err)
}
for _, log := range receipt.Logs {
_, parsedEvent, ok := parser.ParseEvent(*log)
if !ok {
continue
}
event, ok := parsedEvent.(*fastbridge.FastBridgeBridgeRequested)
if ok {
txIDMux.Lock()
txIDs = append(txIDs, event.TransactionId)
txIDMux.Unlock()
return nil
}
}
return nil
})
}
err = g.Wait()
i.NoError(err)
i.Equal(numTxs, len(txIDs))

// TODO: this, but cleaner
anvilClient, err := anvil.Dial(i.GetTestContext(), i.originBackend.RPCAddress())
i.NoError(err)

go func() {
for {
select {
case <-i.GetTestContext().Done():
return
case <-time.After(time.Second * 4):
// increase time by 30 mintutes every second, should be enough to get us a fastish e2e test
// we don't need to worry about deadline since we're only doing this on origin
err = anvilClient.IncreaseTime(i.GetTestContext(), 60*30)
i.NoError(err)

// because can claim works on last block timestamp, we need to do something
err = anvilClient.Mine(i.GetTestContext(), 1)
i.NoError(err)
}
}
}()

// verify that each tx is relayed
i.Eventually(func() bool {
for _, txID := range txIDs {
result, err := i.store.GetQuoteRequestByID(i.GetTestContext(), txID)
if err != nil {
return false
}
if result.Status <= reldb.ProvePosted {
return false
}
}
return true
})
}

0 comments on commit 4fd9ef1

Please sign in to comment.