Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rfq-relayer): highly concurrent e2e test #3042

Merged
merged 7 commits into from
Aug 20, 2024
Merged
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
"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 @@
return result.TxHash == fakeHash && result.Status == guarddb.Disputed && result.TransactionID == txID
})
}

//nolint:gocognit
func (i *IntegrationSuite) TestConcurrentBridges() {

Check failure on line 477 in services/rfq/e2e/rfq_test.go

View workflow job for this annotation

GitHub Actions / Lint (services/rfq)

calculated cyclomatic complexity for function TestConcurrentBridges is 17, max is 10 (cyclop)
// 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
})
}
Loading