Skip to content
This repository was archived by the owner on May 11, 2024. It is now read-only.

Commit 40325bc

Browse files
fix(pkg): fix a bug in transaction sender (#606)
Co-authored-by: David <david@taiko.xyz>
1 parent 1b03e6a commit 40325bc

File tree

7 files changed

+178
-81
lines changed

7 files changed

+178
-81
lines changed

driver/driver_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ func (s *DriverTestSuite) TestCheckL1ReorgToHigherFork() {
164164
s.Equal(l1Head3.Hash(), l1Head1.Hash())
165165

166166
// Because of evm_revert operation, the nonce of the proposer need to be adjusted.
167-
sender.AdjustNonce(nil)
167+
s.Nil(sender.SetNonce(nil, true))
168168
// Propose ten blocks on another fork
169169
for i := 0; i < 10; i++ {
170170
s.ProposeInvalidTxListBytes(s.p)
@@ -225,7 +225,7 @@ func (s *DriverTestSuite) TestCheckL1ReorgToLowerFork() {
225225
s.Equal(l1Head3.Number.Uint64(), l1Head1.Number.Uint64())
226226
s.Equal(l1Head3.Hash(), l1Head1.Hash())
227227

228-
sender.AdjustNonce(nil)
228+
s.Nil(sender.SetNonce(nil, true))
229229
// Propose one blocks on another fork
230230
s.ProposeInvalidTxListBytes(s.p)
231231

@@ -283,7 +283,7 @@ func (s *DriverTestSuite) TestCheckL1ReorgToSameHeightFork() {
283283
s.Equal(l1Head3.Number.Uint64(), l1Head1.Number.Uint64())
284284
s.Equal(l1Head3.Hash(), l1Head1.Hash())
285285

286-
sender.AdjustNonce(nil)
286+
s.Nil(sender.SetNonce(nil, true))
287287
// Propose two blocks on another fork
288288
s.ProposeInvalidTxListBytes(s.p)
289289
time.Sleep(3 * time.Second)

internal/sender/common.go

+25-19
Original file line numberDiff line numberDiff line change
@@ -48,27 +48,33 @@ func (s *Sender) adjustGas(txData types.TxData) {
4848
}
4949
}
5050

51-
// AdjustNonce adjusts the nonce of the given transaction with the current nonce of the sender.
52-
func (s *Sender) AdjustNonce(txData types.TxData) {
53-
nonce, err := s.client.NonceAt(s.ctx, s.Opts.From, nil)
54-
if err != nil {
55-
log.Warn("Failed to get the nonce", "from", s.Opts.From, "err", err)
56-
return
51+
// SetNonce adjusts the nonce of the given transaction with the current nonce of the sender.
52+
func (s *Sender) SetNonce(txData types.TxData, adjust bool) (err error) {
53+
var nonce uint64
54+
if adjust {
55+
s.nonce, err = s.client.NonceAt(s.ctx, s.Opts.From, nil)
56+
if err != nil {
57+
log.Warn("Failed to get the nonce", "from", s.Opts.From, "err", err)
58+
return err
59+
}
5760
}
58-
s.Opts.Nonce = new(big.Int).SetUint64(nonce)
59-
60-
switch tx := txData.(type) {
61-
case *types.DynamicFeeTx:
62-
tx.Nonce = nonce
63-
case *types.BlobTx:
64-
tx.Nonce = nonce
65-
case *types.LegacyTx:
66-
tx.Nonce = nonce
67-
case *types.AccessListTx:
68-
tx.Nonce = nonce
69-
default:
70-
log.Debug("Unsupported transaction type when adjust nonce", "from", s.Opts.From)
61+
nonce = s.nonce
62+
63+
if !utils.IsNil(txData) {
64+
switch tx := txData.(type) {
65+
case *types.DynamicFeeTx:
66+
tx.Nonce = nonce
67+
case *types.BlobTx:
68+
tx.Nonce = nonce
69+
case *types.LegacyTx:
70+
tx.Nonce = nonce
71+
case *types.AccessListTx:
72+
tx.Nonce = nonce
73+
default:
74+
return fmt.Errorf("unsupported transaction type: %v", txData)
75+
}
7176
}
77+
return
7278
}
7379

7480
// updateGasTipGasFee updates the gas tip cap and gas fee cap of the sender with the given chain head info.

internal/sender/sender.go

+70-25
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ type Sender struct {
7878
head *types.Header
7979
client *rpc.EthClient
8080

81-
Opts *bind.TransactOpts
81+
nonce uint64
82+
Opts *bind.TransactOpts
8283

8384
unconfirmedTxs cmap.ConcurrentMap[string, *TxToConfirm]
8485
txToConfirmCh cmap.ConcurrentMap[string, chan *TxToConfirm]
@@ -110,6 +111,12 @@ func NewSender(ctx context.Context, cfg *Config, client *rpc.EthClient, priv *ec
110111
}
111112
}
112113

114+
// Get the nonce
115+
nonce, err := client.NonceAt(ctx, opts.From, nil)
116+
if err != nil {
117+
return nil, err
118+
}
119+
113120
// Get the chain ID
114121
head, err := client.HeaderByNumber(ctx, nil)
115122
if err != nil {
@@ -121,13 +128,12 @@ func NewSender(ctx context.Context, cfg *Config, client *rpc.EthClient, priv *ec
121128
Config: cfg,
122129
head: head,
123130
client: client,
131+
nonce: nonce,
124132
Opts: opts,
125133
unconfirmedTxs: cmap.New[*TxToConfirm](),
126134
txToConfirmCh: cmap.New[chan *TxToConfirm](),
127135
stopCh: make(chan struct{}),
128136
}
129-
// Initialize the nonce
130-
sender.AdjustNonce(nil)
131137

132138
// Initialize the gas fee related fields
133139
if err = sender.updateGasTipGasFee(head); err != nil {
@@ -177,6 +183,10 @@ func (s *Sender) GetUnconfirmedTx(txID string) *types.Transaction {
177183

178184
// SendRawTransaction sends a transaction to the given Ethereum node.
179185
func (s *Sender) SendRawTransaction(nonce uint64, target *common.Address, value *big.Int, data []byte) (string, error) {
186+
if s.unconfirmedTxs.Count() >= unconfirmedTxsCap {
187+
return "", fmt.Errorf("too many pending transactions")
188+
}
189+
180190
gasLimit := s.GasLimit
181191
if gasLimit == 0 {
182192
var err error
@@ -192,16 +202,36 @@ func (s *Sender) SendRawTransaction(nonce uint64, target *common.Address, value
192202
return "", err
193203
}
194204
}
195-
return s.SendTransaction(types.NewTx(&types.DynamicFeeTx{
196-
ChainID: s.client.ChainID,
197-
To: target,
198-
Nonce: nonce,
199-
GasFeeCap: s.Opts.GasFeeCap,
200-
GasTipCap: s.Opts.GasTipCap,
201-
Gas: gasLimit,
202-
Value: value,
203-
Data: data,
204-
}))
205+
206+
txID := uuid.New()
207+
txToConfirm := &TxToConfirm{
208+
ID: txID,
209+
originalTx: &types.DynamicFeeTx{
210+
ChainID: s.client.ChainID,
211+
To: target,
212+
Nonce: nonce,
213+
GasFeeCap: s.Opts.GasFeeCap,
214+
GasTipCap: s.Opts.GasTipCap,
215+
Gas: gasLimit,
216+
Value: value,
217+
Data: data,
218+
},
219+
}
220+
221+
if err := s.send(txToConfirm, false); err != nil && !strings.Contains(err.Error(), "replacement transaction") {
222+
log.Error("Failed to send transaction",
223+
"tx_id", txID,
224+
"nonce", txToConfirm.CurrentTx.Nonce(),
225+
"err", err,
226+
)
227+
return "", err
228+
}
229+
230+
// Add the transaction to the unconfirmed transactions
231+
s.unconfirmedTxs.Set(txID, txToConfirm)
232+
s.txToConfirmCh.Set(txID, make(chan *TxToConfirm, 1))
233+
234+
return txID, nil
205235
}
206236

207237
// SendTransaction sends a transaction to the given Ethereum node.
@@ -222,7 +252,7 @@ func (s *Sender) SendTransaction(tx *types.Transaction) (string, error) {
222252
CurrentTx: tx,
223253
}
224254

225-
if err := s.send(txToConfirm); err != nil && !strings.Contains(err.Error(), "replacement transaction") {
255+
if err := s.send(txToConfirm, true); err != nil && !strings.Contains(err.Error(), "replacement transaction") {
226256
log.Error("Failed to send transaction",
227257
"tx_id", txID,
228258
"nonce", txToConfirm.CurrentTx.Nonce(),
@@ -240,12 +270,19 @@ func (s *Sender) SendTransaction(tx *types.Transaction) (string, error) {
240270
}
241271

242272
// send is the internal method to send the given transaction.
243-
func (s *Sender) send(tx *TxToConfirm) error {
273+
func (s *Sender) send(tx *TxToConfirm, resetNonce bool) error {
244274
s.mu.Lock()
245275
defer s.mu.Unlock()
246276

247277
originalTx := tx.originalTx
248278

279+
if resetNonce {
280+
// Set the nonce of the transaction.
281+
if err := s.SetNonce(originalTx, false); err != nil {
282+
return err
283+
}
284+
}
285+
249286
for i := 0; i < nonceIncorrectRetrys; i++ {
250287
// Retry when nonce is incorrect
251288
rawTx, err := s.Opts.Signer(s.Opts.From, types.NewTx(originalTx))
@@ -258,13 +295,21 @@ func (s *Sender) send(tx *TxToConfirm) error {
258295
// Check if the error is nonce too low
259296
if err != nil {
260297
if strings.Contains(err.Error(), "nonce too low") {
261-
s.AdjustNonce(originalTx)
262-
log.Warn("Nonce is incorrect, retry sending the transaction with new nonce",
263-
"tx_id", tx.ID,
264-
"nonce", tx.CurrentTx.Nonce(),
265-
"hash", rawTx.Hash(),
266-
"err", err,
267-
)
298+
if err := s.SetNonce(originalTx, true); err != nil {
299+
log.Error("Failed to set nonce when appear nonce too low",
300+
"tx_id", tx.ID,
301+
"nonce", tx.CurrentTx.Nonce(),
302+
"hash", rawTx.Hash(),
303+
"err", err,
304+
)
305+
} else {
306+
log.Warn("Nonce is incorrect, retry sending the transaction with new nonce",
307+
"tx_id", tx.ID,
308+
"nonce", tx.CurrentTx.Nonce(),
309+
"hash", rawTx.Hash(),
310+
"err", err,
311+
)
312+
}
268313
continue
269314
}
270315
if strings.Contains(err.Error(), "replacement transaction underpriced") {
@@ -287,7 +332,7 @@ func (s *Sender) send(tx *TxToConfirm) error {
287332
}
288333
break
289334
}
290-
s.Opts.Nonce = new(big.Int).Add(s.Opts.Nonce, common.Big1)
335+
s.nonce++
291336
return nil
292337
}
293338

@@ -340,7 +385,7 @@ func (s *Sender) resendUnconfirmedTxs() {
340385
s.releaseUnconfirmedTx(id)
341386
continue
342387
}
343-
if err := s.send(unconfirmedTx); err != nil {
388+
if err := s.send(unconfirmedTx, true); err != nil {
344389
log.Warn(
345390
"Failed to resend the transaction",
346391
"tx_id", id,
@@ -390,7 +435,7 @@ func (s *Sender) checkPendingTransactionsConfirmation() {
390435
}
391436
pendingTx.Receipt = receipt
392437
if receipt.Status != types.ReceiptStatusSuccessful {
393-
pendingTx.Err = fmt.Errorf("transaction reverted, hash: %s", receipt.TxHash)
438+
pendingTx.Err = fmt.Errorf("transaction status is failed, hash: %s", receipt.TxHash)
394439
s.releaseUnconfirmedTx(id)
395440
continue
396441
}

internal/sender/sender_test.go

+40-2
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,50 @@ type SenderTestSuite struct {
2323
sender *sender.Sender
2424
}
2525

26-
func (s *SenderTestSuite) TestNormalSender() {
26+
func (s *SenderTestSuite) TestSendTransaction() {
27+
var (
28+
opts = s.sender.Opts
29+
client = s.RPCClient.L1
30+
eg errgroup.Group
31+
)
32+
eg.SetLimit(runtime.NumCPU())
33+
for i := 0; i < 8; i++ {
34+
i := i
35+
eg.Go(func() error {
36+
to := common.BigToAddress(big.NewInt(int64(i)))
37+
tx := types.NewTx(&types.DynamicFeeTx{
38+
ChainID: client.ChainID,
39+
To: &to,
40+
GasFeeCap: opts.GasFeeCap,
41+
GasTipCap: opts.GasTipCap,
42+
Gas: 21000000,
43+
Value: big.NewInt(1),
44+
Data: nil,
45+
})
46+
47+
_, err := s.sender.SendTransaction(tx)
48+
return err
49+
})
50+
}
51+
s.Nil(eg.Wait())
52+
53+
for _, confirmCh := range s.sender.TxToConfirmChannels() {
54+
confirm := <-confirmCh
55+
s.Nil(confirm.Err)
56+
}
57+
}
58+
59+
func (s *SenderTestSuite) TestSendRawTransaction() {
60+
nonce, err := s.RPCClient.L1.NonceAt(context.Background(), s.sender.Opts.From, nil)
61+
s.Nil(err)
62+
2763
var eg errgroup.Group
2864
eg.SetLimit(runtime.NumCPU())
2965
for i := 0; i < 5; i++ {
3066
i := i
3167
eg.Go(func() error {
3268
addr := common.BigToAddress(big.NewInt(int64(i)))
33-
_, err := s.sender.SendRawTransaction(s.sender.Opts.Nonce.Uint64(), &addr, big.NewInt(1), nil)
69+
_, err := s.sender.SendRawTransaction(nonce+uint64(i), &addr, big.NewInt(1), nil)
3470
return err
3571
})
3672
}
@@ -121,6 +157,7 @@ func (s *SenderTestSuite) TestNonceTooLow() {
121157

122158
func (s *SenderTestSuite) SetupTest() {
123159
s.ClientTestSuite.SetupTest()
160+
s.SetL1Automine(true)
124161

125162
ctx := context.Background()
126163
priv, err := crypto.ToECDSA(common.FromHex(os.Getenv("L1_PROPOSER_PRIVATE_KEY")))
@@ -137,6 +174,7 @@ func (s *SenderTestSuite) SetupTest() {
137174
}
138175

139176
func (s *SenderTestSuite) TearDownTest() {
177+
s.SetL1Automine(false)
140178
s.sender.Close()
141179
s.ClientTestSuite.TearDownTest()
142180
}

internal/testutils/helper.go

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func (s *ClientTestSuite) ProposeInvalidTxListBytes(proposer Proposer) {
2828
invalidTxListBytes := RandomBytes(256)
2929

3030
s.Nil(proposer.ProposeTxList(context.Background(), invalidTxListBytes, 1))
31+
for _, confirmCh := range proposer.GetSender().TxToConfirmChannels() {
32+
confirm := <-confirmCh
33+
s.Nil(confirm.Err)
34+
}
3135
}
3236

3337
func (s *ClientTestSuite) ProposeAndInsertEmptyBlocks(
@@ -54,6 +58,10 @@ func (s *ClientTestSuite) ProposeAndInsertEmptyBlocks(
5458
s.Nil(err)
5559

5660
s.Nil(proposer.ProposeTxList(context.Background(), encoded, 0))
61+
for _, confirmCh := range proposer.GetSender().TxToConfirmChannels() {
62+
confirm := <-confirmCh
63+
s.Nil(confirm.Err)
64+
}
5765

5866
s.ProposeInvalidTxListBytes(proposer)
5967

0 commit comments

Comments
 (0)