mirror of
https://github.com/scroll-tech/scroll.git
synced 2026-01-21 03:47:59 -05:00
309 lines
9.3 KiB
Go
309 lines
9.3 KiB
Go
package sender
|
|
|
|
import (
|
|
"context"
|
|
"crypto/ecdsa"
|
|
"errors"
|
|
"math/big"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"golang.org/x/sync/errgroup"
|
|
|
|
"github.com/agiledragon/gomonkey/v2"
|
|
cmap "github.com/orcaman/concurrent-map"
|
|
"github.com/scroll-tech/go-ethereum/accounts/abi/bind"
|
|
"github.com/scroll-tech/go-ethereum/common"
|
|
"github.com/scroll-tech/go-ethereum/core/types"
|
|
"github.com/scroll-tech/go-ethereum/crypto"
|
|
"github.com/scroll-tech/go-ethereum/ethclient"
|
|
"github.com/scroll-tech/go-ethereum/rpc"
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"scroll-tech/common/docker"
|
|
|
|
"scroll-tech/bridge/config"
|
|
)
|
|
|
|
const TXBatch = 50
|
|
|
|
var (
|
|
privateKeys []*ecdsa.PrivateKey
|
|
cfg *config.Config
|
|
base *docker.App
|
|
txTypes = []string{"LegacyTx", "AccessListTx", "DynamicFeeTx"}
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
base = docker.NewDockerApp()
|
|
|
|
m.Run()
|
|
|
|
base.Free()
|
|
}
|
|
|
|
func setupEnv(t *testing.T) {
|
|
var err error
|
|
cfg, err = config.NewConfig("../config.json")
|
|
assert.NoError(t, err)
|
|
base.RunImages(t)
|
|
priv, err := crypto.HexToECDSA("1212121212121212121212121212121212121212121212121212121212121212")
|
|
assert.NoError(t, err)
|
|
// Load default private key.
|
|
privateKeys = []*ecdsa.PrivateKey{priv}
|
|
|
|
cfg.L1Config.RelayerConfig.SenderConfig.Endpoint = base.L1gethImg.Endpoint()
|
|
cfg.L1Config.RelayerConfig.SenderConfig.CheckBalanceTime = 1
|
|
}
|
|
|
|
func TestSender(t *testing.T) {
|
|
// Setup
|
|
setupEnv(t)
|
|
|
|
t.Run("test new sender", testNewSender)
|
|
t.Run("test pending limit", testPendLimit)
|
|
|
|
t.Run("test min gas limit", testMinGasLimit)
|
|
t.Run("test resubmit transaction", testResubmitTransaction)
|
|
t.Run("test check pending transaction", testCheckPendingTransaction)
|
|
|
|
t.Run("test 1 account sender", func(t *testing.T) { testBatchSender(t, 1) })
|
|
t.Run("test 3 account sender", func(t *testing.T) { testBatchSender(t, 3) })
|
|
t.Run("test 8 account sender", func(t *testing.T) { testBatchSender(t, 8) })
|
|
}
|
|
|
|
func testNewSender(t *testing.T) {
|
|
for _, txType := range txTypes {
|
|
// exit by Stop()
|
|
cfgCopy1 := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy1.TxType = txType
|
|
newSender1, err := NewSender(context.Background(), &cfgCopy1, privateKeys)
|
|
assert.NoError(t, err)
|
|
newSender1.Stop()
|
|
|
|
// exit by ctx.Done()
|
|
cfgCopy2 := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy2.TxType = txType
|
|
subCtx, cancel := context.WithCancel(context.Background())
|
|
_, err = NewSender(subCtx, &cfgCopy2, privateKeys)
|
|
assert.NoError(t, err)
|
|
cancel()
|
|
}
|
|
}
|
|
|
|
func testPendLimit(t *testing.T) {
|
|
for _, txType := range txTypes {
|
|
cfgCopy := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy.TxType = txType
|
|
cfgCopy.Confirmations = rpc.LatestBlockNumber
|
|
cfgCopy.PendingLimit = 2
|
|
newSender, err := NewSender(context.Background(), &cfgCopy, privateKeys)
|
|
assert.NoError(t, err)
|
|
|
|
for i := 0; i < 2*newSender.PendingLimit(); i++ {
|
|
_, err = newSender.SendTransaction(strconv.Itoa(i), &common.Address{}, big.NewInt(1), nil, 0)
|
|
assert.True(t, err == nil || (err != nil && err.Error() == "sender's pending pool is full"))
|
|
}
|
|
assert.True(t, newSender.PendingCount() <= newSender.PendingLimit())
|
|
newSender.Stop()
|
|
}
|
|
}
|
|
|
|
func testMinGasLimit(t *testing.T) {
|
|
for _, txType := range txTypes {
|
|
cfgCopy := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy.TxType = txType
|
|
cfgCopy.Confirmations = rpc.LatestBlockNumber
|
|
newSender, err := NewSender(context.Background(), &cfgCopy, privateKeys)
|
|
assert.NoError(t, err)
|
|
|
|
client, err := ethclient.Dial(cfgCopy.Endpoint)
|
|
assert.NoError(t, err)
|
|
|
|
// MinGasLimit = 0
|
|
txHash0, err := newSender.SendTransaction("0", &common.Address{}, big.NewInt(1), nil, 0)
|
|
assert.NoError(t, err)
|
|
tx0, _, err := client.TransactionByHash(context.Background(), txHash0)
|
|
assert.NoError(t, err)
|
|
assert.Greater(t, tx0.Gas(), uint64(0))
|
|
|
|
// MinGasLimit = 100000
|
|
txHash1, err := newSender.SendTransaction("1", &common.Address{}, big.NewInt(1), nil, 100000)
|
|
assert.NoError(t, err)
|
|
tx1, _, err := client.TransactionByHash(context.Background(), txHash1)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tx1.Gas(), uint64(150000))
|
|
|
|
newSender.Stop()
|
|
}
|
|
}
|
|
|
|
func testResubmitTransaction(t *testing.T) {
|
|
for _, txType := range txTypes {
|
|
cfgCopy := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy.TxType = txType
|
|
s, err := NewSender(context.Background(), &cfgCopy, privateKeys)
|
|
assert.NoError(t, err)
|
|
auth := s.auths.getAccount()
|
|
tx := types.NewTransaction(auth.Nonce.Uint64(), common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil)
|
|
feeData, err := s.getFeeData(auth, &common.Address{}, big.NewInt(0), nil, 0)
|
|
assert.NoError(t, err)
|
|
_, err = s.resubmitTransaction(feeData, auth, tx)
|
|
assert.NoError(t, err)
|
|
s.Stop()
|
|
}
|
|
}
|
|
|
|
func testCheckPendingTransaction(t *testing.T) {
|
|
for _, txType := range txTypes {
|
|
cfgCopy := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy.TxType = txType
|
|
s, err := NewSender(context.Background(), &cfgCopy, privateKeys)
|
|
assert.NoError(t, err)
|
|
|
|
header := &types.Header{Number: big.NewInt(100), BaseFee: big.NewInt(100)}
|
|
confirmed := uint64(100)
|
|
receipt := &types.Receipt{Status: types.ReceiptStatusSuccessful, BlockNumber: big.NewInt(90)}
|
|
auth := s.auths.getAccount()
|
|
tx := types.NewTransaction(auth.Nonce.Uint64(), common.Address{}, big.NewInt(0), 0, big.NewInt(0), nil)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
receipt *types.Receipt
|
|
receiptErr error
|
|
resubmitErr error
|
|
expectedCount int
|
|
expectedFound bool
|
|
}{
|
|
{
|
|
name: "Normal case, transaction receipt exists and successful",
|
|
receipt: receipt,
|
|
receiptErr: nil,
|
|
resubmitErr: nil,
|
|
expectedCount: 0,
|
|
expectedFound: false,
|
|
},
|
|
{
|
|
name: "Resubmit case, resubmitTransaction error (not nonce) case",
|
|
receipt: receipt,
|
|
receiptErr: errors.New("receipt error"),
|
|
resubmitErr: errors.New("resubmit error"),
|
|
expectedCount: 1,
|
|
expectedFound: true,
|
|
},
|
|
{
|
|
name: "Resubmit case, resubmitTransaction success case",
|
|
receipt: receipt,
|
|
receiptErr: errors.New("receipt error"),
|
|
resubmitErr: nil,
|
|
expectedCount: 1,
|
|
expectedFound: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
var c *ethclient.Client
|
|
patchGuard := gomonkey.ApplyMethodFunc(c, "TransactionReceipt", func(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
|
|
return tc.receipt, tc.receiptErr
|
|
})
|
|
patchGuard.ApplyPrivateMethod(s, "resubmitTransaction",
|
|
func(feeData *FeeData, auth *bind.TransactOpts, tx *types.Transaction) (*types.Transaction, error) {
|
|
return tx, tc.resubmitErr
|
|
},
|
|
)
|
|
|
|
pendingTx := &PendingTransaction{id: "abc", tx: tx, submitAt: header.Number.Uint64() - s.config.EscalateBlocks - 1}
|
|
s.pendingTxs.Set(pendingTx.id, pendingTx)
|
|
s.checkPendingTransaction(header, confirmed)
|
|
|
|
if tc.receiptErr == nil {
|
|
expectedConfirmation := &Confirmation{
|
|
ID: pendingTx.id,
|
|
IsSuccessful: tc.receipt.Status == types.ReceiptStatusSuccessful,
|
|
TxHash: pendingTx.tx.Hash(),
|
|
}
|
|
actualConfirmation := <-s.confirmCh
|
|
assert.Equal(t, expectedConfirmation, actualConfirmation)
|
|
}
|
|
|
|
if tc.expectedFound && tc.resubmitErr == nil {
|
|
actualPendingTx, found := s.pendingTxs.Get(pendingTx.id)
|
|
assert.Equal(t, true, found)
|
|
assert.Equal(t, header.Number.Uint64(), actualPendingTx.submitAt)
|
|
}
|
|
|
|
_, found := s.pendingTxs.Get(pendingTx.id)
|
|
assert.Equal(t, tc.expectedFound, found)
|
|
assert.Equal(t, tc.expectedCount, s.pendingTxs.Count())
|
|
patchGuard.Reset()
|
|
})
|
|
}
|
|
s.Stop()
|
|
}
|
|
}
|
|
|
|
func testBatchSender(t *testing.T, batchSize int) {
|
|
for _, txType := range txTypes {
|
|
for len(privateKeys) < batchSize {
|
|
priv, err := crypto.GenerateKey()
|
|
assert.NoError(t, err)
|
|
privateKeys = append(privateKeys, priv)
|
|
}
|
|
|
|
cfgCopy := *cfg.L1Config.RelayerConfig.SenderConfig
|
|
cfgCopy.Confirmations = rpc.LatestBlockNumber
|
|
cfgCopy.PendingLimit = batchSize * TXBatch
|
|
cfgCopy.TxType = txType
|
|
newSender, err := NewSender(context.Background(), &cfgCopy, privateKeys)
|
|
assert.NoError(t, err)
|
|
|
|
// send transactions
|
|
var (
|
|
eg errgroup.Group
|
|
idCache = cmap.New()
|
|
confirmCh = newSender.ConfirmChan()
|
|
)
|
|
for idx := 0; idx < newSender.NumberOfAccounts(); idx++ {
|
|
index := idx
|
|
eg.Go(func() error {
|
|
for i := 0; i < TXBatch; i++ {
|
|
toAddr := common.HexToAddress("0x4592d8f8d7b001e72cb26a73e4fa1806a51ac79d")
|
|
id := strconv.Itoa(i + index*1000)
|
|
_, err := newSender.SendTransaction(id, &toAddr, big.NewInt(1), nil, 0)
|
|
if errors.Is(err, ErrNoAvailableAccount) || errors.Is(err, ErrFullPending) {
|
|
<-time.After(time.Second)
|
|
continue
|
|
}
|
|
assert.NoError(t, err)
|
|
idCache.Set(id, struct{}{})
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
assert.NoError(t, eg.Wait())
|
|
t.Logf("successful send batch txs, batch size: %d, total count: %d", newSender.NumberOfAccounts(), TXBatch*newSender.NumberOfAccounts())
|
|
|
|
// avoid 10 mins cause testcase panic
|
|
after := time.After(80 * time.Second)
|
|
isDone := false
|
|
for !isDone {
|
|
select {
|
|
case cmsg := <-confirmCh:
|
|
assert.Equal(t, true, cmsg.IsSuccessful)
|
|
_, exist := idCache.Pop(cmsg.ID)
|
|
assert.Equal(t, true, exist)
|
|
// Receive all confirmed txs.
|
|
if idCache.Count() == 0 {
|
|
isDone = true
|
|
}
|
|
case <-after:
|
|
t.Error("newSender test failed because of timeout")
|
|
isDone = true
|
|
}
|
|
}
|
|
newSender.Stop()
|
|
}
|
|
}
|