arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/coordinator/txmanager.go
coordinator/txmanager.goRaw
  1package coordinator
  2
  3import (
  4	"context"
  5	"fmt"
  6	"math/big"
  7	"strings"
  8	"time"
  9
 10	"github.com/ethereum/go-ethereum"
 11	"github.com/ethereum/go-ethereum/accounts"
 12	"github.com/ethereum/go-ethereum/accounts/abi/bind"
 13	"github.com/ethereum/go-ethereum/core"
 14	"github.com/ethereum/go-ethereum/core/types"
 15	"github.com/hermeznetwork/hermez-node/common"
 16	"github.com/hermeznetwork/hermez-node/db/l2db"
 17	"github.com/hermeznetwork/hermez-node/eth"
 18	"github.com/hermeznetwork/hermez-node/log"
 19	"github.com/hermeznetwork/hermez-node/synchronizer"
 20	"github.com/hermeznetwork/tracerr"
 21)
 22
 23// TxManager handles everything related to ethereum transactions:  It makes the
 24// call to forge, waits for transaction confirmation, and keeps checking them
 25// until a number of confirmed blocks have passed.
 26type TxManager struct {
 27	cfg       Config
 28	ethClient eth.ClientInterface
 29	l2DB      *l2db.L2DB   // Used only to mark forged txs as forged in the L2DB
 30	coord     *Coordinator // Used only to send messages to stop the pipeline
 31	batchCh   chan *BatchInfo
 32	chainID   *big.Int
 33	account   accounts.Account
 34	consts    synchronizer.SCConsts
 35
 36	stats       synchronizer.Stats
 37	vars        synchronizer.SCVariables
 38	statsVarsCh chan statsVars
 39
 40	discardPipelineCh chan int // int refers to the pipelineNum
 41
 42	minPipelineNum int
 43	queue          Queue
 44	// lastSuccessBatch stores the last BatchNum that who's forge call was confirmed
 45	lastSuccessBatch common.BatchNum
 46	// lastPendingBatch common.BatchNum
 47	// accNonce is the account nonce in the last mined block (due to mined txs)
 48	accNonce uint64
 49	// accNextNonce is the nonce that we should use to send the next tx.
 50	// In some cases this will be a reused nonce of an already pending tx.
 51	accNextNonce uint64
 52
 53	lastSentL1BatchBlockNum int64
 54}
 55
 56// NewTxManager creates a new TxManager
 57func NewTxManager(ctx context.Context, cfg *Config, ethClient eth.ClientInterface, l2DB *l2db.L2DB,
 58	coord *Coordinator, scConsts *synchronizer.SCConsts, initSCVars *synchronizer.SCVariables) (
 59	*TxManager, error) {
 60	chainID, err := ethClient.EthChainID()
 61	if err != nil {
 62		return nil, tracerr.Wrap(err)
 63	}
 64	address, err := ethClient.EthAddress()
 65	if err != nil {
 66		return nil, tracerr.Wrap(err)
 67	}
 68	accNonce, err := ethClient.EthNonceAt(ctx, *address, nil)
 69	if err != nil {
 70		return nil, err
 71	}
 72	log.Infow("TxManager started", "nonce", accNonce)
 73	return &TxManager{
 74		cfg:               *cfg,
 75		ethClient:         ethClient,
 76		l2DB:              l2DB,
 77		coord:             coord,
 78		batchCh:           make(chan *BatchInfo, queueLen),
 79		statsVarsCh:       make(chan statsVars, queueLen),
 80		discardPipelineCh: make(chan int, queueLen),
 81		account: accounts.Account{
 82			Address: *address,
 83		},
 84		chainID: chainID,
 85		consts:  *scConsts,
 86
 87		vars: *initSCVars,
 88
 89		minPipelineNum: 0,
 90		queue:          NewQueue(),
 91		accNonce:       accNonce,
 92		accNextNonce:   accNonce,
 93	}, nil
 94}
 95
 96// AddBatch is a thread safe method to pass a new batch TxManager to be sent to
 97// the smart contract via the forge call
 98func (t *TxManager) AddBatch(ctx context.Context, batchInfo *BatchInfo) {
 99	select {
100	case t.batchCh <- batchInfo:
101	case <-ctx.Done():
102	}
103}
104
105// SetSyncStatsVars is a thread safe method to sets the synchronizer Stats
106func (t *TxManager) SetSyncStatsVars(ctx context.Context, stats *synchronizer.Stats,
107	vars *synchronizer.SCVariablesPtr) {
108	select {
109	case t.statsVarsCh <- statsVars{Stats: *stats, Vars: *vars}:
110	case <-ctx.Done():
111	}
112}
113
114// DiscardPipeline is a thread safe method to notify about a discarded pipeline
115// due to a reorg
116func (t *TxManager) DiscardPipeline(ctx context.Context, pipelineNum int) {
117	select {
118	case t.discardPipelineCh <- pipelineNum:
119	case <-ctx.Done():
120	}
121}
122
123func (t *TxManager) syncSCVars(vars synchronizer.SCVariablesPtr) {
124	updateSCVars(&t.vars, vars)
125}
126
127// NewAuth generates a new auth object for an ethereum transaction
128func (t *TxManager) NewAuth(ctx context.Context, batchInfo *BatchInfo) (*bind.TransactOpts, error) {
129	gasPrice, err := t.ethClient.EthSuggestGasPrice(ctx)
130	if err != nil {
131		return nil, tracerr.Wrap(err)
132	}
133	if t.cfg.GasPriceIncPerc != 0 {
134		inc := new(big.Int).Set(gasPrice)
135		inc.Mul(inc, new(big.Int).SetInt64(t.cfg.GasPriceIncPerc))
136		// nolint reason: to calculate percentages we use 100
137		inc.Div(inc, new(big.Int).SetUint64(100)) //nolint:gomnd
138		gasPrice.Add(gasPrice, inc)
139	}
140
141	// log.Debugw("TxManager: transaction metadata", "gasPrice", gasPrice)
142
143	auth, err := bind.NewKeyStoreTransactorWithChainID(t.ethClient.EthKeyStore(), t.account, t.chainID)
144	if err != nil {
145		return nil, tracerr.Wrap(err)
146	}
147	auth.Value = big.NewInt(0) // in wei
148
149	gasLimit := t.cfg.ForgeBatchGasCost.Fixed +
150		uint64(len(batchInfo.L1UserTxsExtra))*t.cfg.ForgeBatchGasCost.L1UserTx +
151		uint64(len(batchInfo.L1CoordTxs))*t.cfg.ForgeBatchGasCost.L1CoordTx +
152		uint64(len(batchInfo.L2Txs))*t.cfg.ForgeBatchGasCost.L2Tx
153	auth.GasLimit = gasLimit
154	auth.GasPrice = gasPrice
155	auth.Nonce = nil
156
157	return auth, nil
158}
159
160func (t *TxManager) shouldSendRollupForgeBatch(batchInfo *BatchInfo) error {
161	nextBlock := t.stats.Eth.LastBlock.Num + 1
162	if !t.canForgeAt(nextBlock) {
163		return tracerr.Wrap(fmt.Errorf("can't forge in the next block: %v", nextBlock))
164	}
165	if t.mustL1L2Batch(nextBlock) && !batchInfo.L1Batch {
166		return tracerr.Wrap(fmt.Errorf("can't forge non-L1Batch in the next block: %v", nextBlock))
167	}
168	margin := t.cfg.SendBatchBlocksMarginCheck
169	if margin != 0 {
170		if !t.canForgeAt(nextBlock + margin) {
171			return tracerr.Wrap(fmt.Errorf("can't forge after %v blocks: %v",
172				margin, nextBlock))
173		}
174		if t.mustL1L2Batch(nextBlock+margin) && !batchInfo.L1Batch {
175			return tracerr.Wrap(fmt.Errorf("can't forge non-L1Batch after %v blocks: %v",
176				margin, nextBlock))
177		}
178	}
179	return nil
180}
181
182func addPerc(v *big.Int, p int64) *big.Int {
183	r := new(big.Int).Set(v)
184	r.Mul(r, big.NewInt(p))
185	// nolint reason: to calculate percentages we divide by 100
186	r.Div(r, big.NewInt(100)) //nolit:gomnd
187	return r.Add(v, r)
188}
189
190func (t *TxManager) sendRollupForgeBatch(ctx context.Context, batchInfo *BatchInfo,
191	resend bool) error {
192	var ethTx *types.Transaction
193	var err error
194	auth, err := t.NewAuth(ctx, batchInfo)
195	if err != nil {
196		return tracerr.Wrap(err)
197	}
198	auth.Nonce = big.NewInt(int64(t.accNextNonce))
199	if resend {
200		auth.Nonce = big.NewInt(int64(batchInfo.EthTx.Nonce()))
201	}
202	for attempt := 0; attempt < t.cfg.EthClientAttempts; attempt++ {
203		if auth.GasPrice.Cmp(t.cfg.MaxGasPrice) > 0 {
204			return tracerr.Wrap(fmt.Errorf("calculated gasPrice (%v) > maxGasPrice (%v)",
205				auth.GasPrice, t.cfg.MaxGasPrice))
206		}
207		// RollupForgeBatch() calls ethclient.SendTransaction()
208		ethTx, err = t.ethClient.RollupForgeBatch(batchInfo.ForgeBatchArgs, auth)
209		// We check the errors via strings because we match the
210		// definition of the error from geth, with the string returned
211		// via RPC obtained by the client.
212		if err == nil {
213			break
214		} else if strings.Contains(err.Error(), core.ErrNonceTooLow.Error()) {
215			log.Warnw("TxManager ethClient.RollupForgeBatch incrementing nonce",
216				"err", err, "nonce", auth.Nonce, "batchNum", batchInfo.BatchNum)
217			auth.Nonce.Add(auth.Nonce, big.NewInt(1))
218			attempt--
219		} else if strings.Contains(err.Error(), core.ErrNonceTooHigh.Error()) {
220			log.Warnw("TxManager ethClient.RollupForgeBatch decrementing nonce",
221				"err", err, "nonce", auth.Nonce, "batchNum", batchInfo.BatchNum)
222			auth.Nonce.Sub(auth.Nonce, big.NewInt(1))
223			attempt--
224		} else if strings.Contains(err.Error(), core.ErrReplaceUnderpriced.Error()) {
225			log.Warnw("TxManager ethClient.RollupForgeBatch incrementing gasPrice",
226				"err", err, "gasPrice", auth.GasPrice, "batchNum", batchInfo.BatchNum)
227			auth.GasPrice = addPerc(auth.GasPrice, 10)
228			attempt--
229		} else if strings.Contains(err.Error(), core.ErrUnderpriced.Error()) {
230			log.Warnw("TxManager ethClient.RollupForgeBatch incrementing gasPrice",
231				"err", err, "gasPrice", auth.GasPrice, "batchNum", batchInfo.BatchNum)
232			auth.GasPrice = addPerc(auth.GasPrice, 10)
233			attempt--
234		} else {
235			log.Errorw("TxManager ethClient.RollupForgeBatch",
236				"attempt", attempt, "err", err, "block", t.stats.Eth.LastBlock.Num+1,
237				"batchNum", batchInfo.BatchNum)
238		}
239		select {
240		case <-ctx.Done():
241			return tracerr.Wrap(common.ErrDone)
242		case <-time.After(t.cfg.EthClientAttemptsDelay):
243		}
244	}
245	if err != nil {
246		return tracerr.Wrap(fmt.Errorf("reached max attempts for ethClient.RollupForgeBatch: %w", err))
247	}
248	if !resend {
249		t.accNextNonce = auth.Nonce.Uint64() + 1
250	}
251	batchInfo.EthTx = ethTx
252	log.Infow("TxManager ethClient.RollupForgeBatch", "batch", batchInfo.BatchNum, "tx", ethTx.Hash())
253	now := time.Now()
254	batchInfo.SendTimestamp = now
255
256	if resend {
257		batchInfo.Debug.ResendNum++
258	}
259	batchInfo.Debug.Status = StatusSent
260	batchInfo.Debug.SendBlockNum = t.stats.Eth.LastBlock.Num + 1
261	batchInfo.Debug.SendTimestamp = batchInfo.SendTimestamp
262	batchInfo.Debug.StartToSendDelay = batchInfo.Debug.SendTimestamp.Sub(
263		batchInfo.Debug.StartTimestamp).Seconds()
264	t.cfg.debugBatchStore(batchInfo)
265
266	if !resend {
267		if batchInfo.L1Batch {
268			t.lastSentL1BatchBlockNum = t.stats.Eth.LastBlock.Num + 1
269		}
270	}
271	if err := t.l2DB.DoneForging(common.TxIDsFromL2Txs(batchInfo.L2Txs),
272		batchInfo.BatchNum); err != nil {
273		return tracerr.Wrap(err)
274	}
275	return nil
276}
277
278// checkEthTransactionReceipt takes the txHash from the BatchInfo and stores
279// the corresponding receipt if found
280func (t *TxManager) checkEthTransactionReceipt(ctx context.Context, batchInfo *BatchInfo) error {
281	txHash := batchInfo.EthTx.Hash()
282	var receipt *types.Receipt
283	var err error
284	for attempt := 0; attempt < t.cfg.EthClientAttempts; attempt++ {
285		receipt, err = t.ethClient.EthTransactionReceipt(ctx, txHash)
286		if ctx.Err() != nil {
287			continue
288		} else if tracerr.Unwrap(err) == ethereum.NotFound {
289			err = nil
290			break
291		} else if err != nil {
292			log.Errorw("TxManager ethClient.EthTransactionReceipt",
293				"attempt", attempt, "err", err)
294		} else {
295			break
296		}
297		select {
298		case <-ctx.Done():
299			return tracerr.Wrap(common.ErrDone)
300		case <-time.After(t.cfg.EthClientAttemptsDelay):
301		}
302	}
303	if err != nil {
304		return tracerr.Wrap(
305			fmt.Errorf("reached max attempts for ethClient.EthTransactionReceipt: %w",
306				err))
307	}
308	batchInfo.Receipt = receipt
309	t.cfg.debugBatchStore(batchInfo)
310	return nil
311}
312
313func (t *TxManager) handleReceipt(ctx context.Context, batchInfo *BatchInfo) (*int64, error) {
314	receipt := batchInfo.Receipt
315	if receipt != nil {
316		if batchInfo.EthTx.Nonce()+1 > t.accNonce {
317			t.accNonce = batchInfo.EthTx.Nonce() + 1
318		}
319		if receipt.Status == types.ReceiptStatusFailed {
320			batchInfo.Debug.Status = StatusFailed
321			_, err := t.ethClient.EthCall(ctx, batchInfo.EthTx, receipt.BlockNumber)
322			log.Warnw("TxManager receipt status is failed", "tx", receipt.TxHash,
323				"batch", batchInfo.BatchNum, "block", receipt.BlockNumber.Int64(),
324				"err", err)
325			batchInfo.EthTxErr = err
326			if batchInfo.BatchNum <= t.lastSuccessBatch {
327				t.lastSuccessBatch = batchInfo.BatchNum - 1
328			}
329			t.cfg.debugBatchStore(batchInfo)
330			return nil, tracerr.Wrap(fmt.Errorf(
331				"ethereum transaction receipt status is failed: %w", err))
332		} else if receipt.Status == types.ReceiptStatusSuccessful {
333			batchInfo.Debug.Status = StatusMined
334			batchInfo.Debug.MineBlockNum = receipt.BlockNumber.Int64()
335			batchInfo.Debug.StartToMineBlocksDelay = batchInfo.Debug.MineBlockNum -
336				batchInfo.Debug.StartBlockNum
337			if batchInfo.Debug.StartToMineDelay == 0 {
338				if block, err := t.ethClient.EthBlockByNumber(ctx,
339					receipt.BlockNumber.Int64()); err != nil {
340					log.Warnw("TxManager: ethClient.EthBlockByNumber", "err", err)
341				} else {
342					batchInfo.Debug.SendToMineDelay = block.Timestamp.Sub(
343						batchInfo.Debug.SendTimestamp).Seconds()
344					batchInfo.Debug.StartToMineDelay = block.Timestamp.Sub(
345						batchInfo.Debug.StartTimestamp).Seconds()
346				}
347			}
348			t.cfg.debugBatchStore(batchInfo)
349			if batchInfo.BatchNum > t.lastSuccessBatch {
350				t.lastSuccessBatch = batchInfo.BatchNum
351			}
352			confirm := t.stats.Eth.LastBlock.Num - receipt.BlockNumber.Int64()
353			return &confirm, nil
354		}
355	}
356	return nil, nil
357}
358
359// TODO:
360// - After sending a message: CancelPipeline, stop all consecutive pending Batches (transactions)
361
362// Queue of BatchInfos
363type Queue struct {
364	list []*BatchInfo
365	// nonceByBatchNum map[common.BatchNum]uint64
366	next int
367}
368
369// NewQueue returns a new queue
370func NewQueue() Queue {
371	return Queue{
372		list: make([]*BatchInfo, 0),
373		// nonceByBatchNum: make(map[common.BatchNum]uint64),
374		next: 0,
375	}
376}
377
378// Len is the length of the queue
379func (q *Queue) Len() int {
380	return len(q.list)
381}
382
383// At returns the BatchInfo at position (or nil if position is out of bounds)
384func (q *Queue) At(position int) *BatchInfo {
385	if position >= len(q.list) {
386		return nil
387	}
388	return q.list[position]
389}
390
391// Next returns the next BatchInfo (or nil if queue is empty)
392func (q *Queue) Next() (int, *BatchInfo) {
393	if len(q.list) == 0 {
394		return 0, nil
395	}
396	defer func() { q.next = (q.next + 1) % len(q.list) }()
397	return q.next, q.list[q.next]
398}
399
400// Remove removes the BatchInfo at position
401func (q *Queue) Remove(position int) {
402	// batchInfo := q.list[position]
403	// delete(q.nonceByBatchNum, batchInfo.BatchNum)
404	q.list = append(q.list[:position], q.list[position+1:]...)
405	if len(q.list) == 0 {
406		q.next = 0
407	} else {
408		q.next = position % len(q.list)
409	}
410}
411
412// Push adds a new BatchInfo
413func (q *Queue) Push(batchInfo *BatchInfo) {
414	q.list = append(q.list, batchInfo)
415	// q.nonceByBatchNum[batchInfo.BatchNum] = batchInfo.EthTx.Nonce()
416}
417
418// func (q *Queue) NonceByBatchNum(batchNum common.BatchNum) (uint64, bool) {
419// 	nonce, ok := q.nonceByBatchNum[batchNum]
420// 	return nonce, ok
421// }
422
423// Run the TxManager
424func (t *TxManager) Run(ctx context.Context) {
425	var statsVars statsVars
426	select {
427	case statsVars = <-t.statsVarsCh:
428	case <-ctx.Done():
429	}
430	t.stats = statsVars.Stats
431	t.syncSCVars(statsVars.Vars)
432	log.Infow("TxManager: received initial statsVars",
433		"block", t.stats.Eth.LastBlock.Num, "batch", t.stats.Eth.LastBatchNum)
434
435	timer := time.NewTimer(longWaitDuration)
436	for {
437		select {
438		case <-ctx.Done():
439			log.Info("TxManager done")
440			return
441		case statsVars := <-t.statsVarsCh:
442			t.stats = statsVars.Stats
443			t.syncSCVars(statsVars.Vars)
444		case pipelineNum := <-t.discardPipelineCh:
445			t.minPipelineNum = pipelineNum + 1
446			if err := t.removeBadBatchInfos(ctx); ctx.Err() != nil {
447				continue
448			} else if err != nil {
449				log.Errorw("TxManager: removeBadBatchInfos", "err", err)
450				continue
451			}
452		case batchInfo := <-t.batchCh:
453			if batchInfo.PipelineNum < t.minPipelineNum {
454				log.Warnw("TxManager: batchInfo received pipelineNum < minPipelineNum",
455					"num", batchInfo.PipelineNum, "minNum", t.minPipelineNum)
456			}
457			if err := t.shouldSendRollupForgeBatch(batchInfo); err != nil {
458				log.Warnw("TxManager: shouldSend", "err", err,
459					"batch", batchInfo.BatchNum)
460				t.coord.SendMsg(ctx, MsgStopPipeline{
461					Reason: fmt.Sprintf("forgeBatch shouldSend: %v", err)})
462				continue
463			}
464			if err := t.sendRollupForgeBatch(ctx, batchInfo, false); ctx.Err() != nil {
465				continue
466			} else if err != nil {
467				// If we reach here it's because our ethNode has
468				// been unable to send the transaction to
469				// ethereum.  This could be due to the ethNode
470				// failure, or an invalid transaction (that
471				// can't be mined)
472				log.Warnw("TxManager: forgeBatch send failed", "err", err,
473					"batch", batchInfo.BatchNum)
474				t.coord.SendMsg(ctx, MsgStopPipeline{
475					Reason: fmt.Sprintf("forgeBatch send: %v", err)})
476				continue
477			}
478			t.queue.Push(batchInfo)
479			if !timer.Stop() {
480				<-timer.C
481			}
482			timer.Reset(t.cfg.TxManagerCheckInterval)
483		case <-timer.C:
484			queuePosition, batchInfo := t.queue.Next()
485			if batchInfo == nil {
486				timer.Reset(longWaitDuration)
487				continue
488			}
489			timer.Reset(t.cfg.TxManagerCheckInterval)
490			if err := t.checkEthTransactionReceipt(ctx, batchInfo); ctx.Err() != nil {
491				continue
492			} else if err != nil { //nolint:staticcheck
493				// Our ethNode is giving an error different
494				// than "not found" when getting the receipt
495				// for the transaction, so we can't figure out
496				// if it was not mined, mined and succesfull or
497				// mined and failed.  This could be due to the
498				// ethNode failure.
499				t.coord.SendMsg(ctx, MsgStopPipeline{
500					Reason: fmt.Sprintf("forgeBatch receipt: %v", err)})
501			}
502
503			confirm, err := t.handleReceipt(ctx, batchInfo)
504			if ctx.Err() != nil {
505				continue
506			} else if err != nil { //nolint:staticcheck
507				// Transaction was rejected
508				if err := t.removeBadBatchInfos(ctx); ctx.Err() != nil {
509					continue
510				} else if err != nil {
511					log.Errorw("TxManager: removeBadBatchInfos", "err", err)
512					continue
513				}
514				t.coord.SendMsg(ctx, MsgStopPipeline{
515					Reason: fmt.Sprintf("forgeBatch reject: %v", err)})
516				continue
517			}
518			now := time.Now()
519			if !t.cfg.EthNoReuseNonce && confirm == nil &&
520				now.Sub(batchInfo.SendTimestamp) > t.cfg.EthTxResendTimeout {
521				log.Infow("TxManager: forgeBatch tx not been mined timeout, resending",
522					"tx", batchInfo.EthTx.Hash(), "batch", batchInfo.BatchNum)
523				if err := t.sendRollupForgeBatch(ctx, batchInfo, true); ctx.Err() != nil {
524					continue
525				} else if err != nil {
526					// If we reach here it's because our ethNode has
527					// been unable to send the transaction to
528					// ethereum.  This could be due to the ethNode
529					// failure, or an invalid transaction (that
530					// can't be mined)
531					log.Warnw("TxManager: forgeBatch resend failed", "err", err,
532						"batch", batchInfo.BatchNum)
533					t.coord.SendMsg(ctx, MsgStopPipeline{
534						Reason: fmt.Sprintf("forgeBatch resend: %v", err)})
535					continue
536				}
537			}
538
539			if confirm != nil && *confirm >= t.cfg.ConfirmBlocks {
540				log.Debugw("TxManager: forgeBatch tx confirmed",
541					"tx", batchInfo.EthTx.Hash(), "batch", batchInfo.BatchNum)
542				t.queue.Remove(queuePosition)
543			}
544		}
545	}
546}
547
548func (t *TxManager) removeBadBatchInfos(ctx context.Context) error {
549	next := 0
550	for {
551		batchInfo := t.queue.At(next)
552		if batchInfo == nil {
553			break
554		}
555		if err := t.checkEthTransactionReceipt(ctx, batchInfo); ctx.Err() != nil {
556			return nil
557		} else if err != nil {
558			// Our ethNode is giving an error different
559			// than "not found" when getting the receipt
560			// for the transaction, so we can't figure out
561			// if it was not mined, mined and succesfull or
562			// mined and failed.  This could be due to the
563			// ethNode failure.
564			next++
565			continue
566		}
567		confirm, err := t.handleReceipt(ctx, batchInfo)
568		if ctx.Err() != nil {
569			return nil
570		} else if err != nil {
571			// Transaction was rejected
572			if t.minPipelineNum <= batchInfo.PipelineNum {
573				t.minPipelineNum = batchInfo.PipelineNum + 1
574			}
575			t.queue.Remove(next)
576			continue
577		}
578		// If tx is pending but is from a cancelled pipeline, remove it
579		// from the queue
580		if confirm == nil {
581			if batchInfo.PipelineNum < t.minPipelineNum {
582				t.queue.Remove(next)
583				continue
584			}
585		}
586		next++
587	}
588	accNonce, err := t.ethClient.EthNonceAt(ctx, t.account.Address, nil)
589	if err != nil {
590		return err
591	}
592	if !t.cfg.EthNoReuseNonce {
593		t.accNextNonce = accNonce
594	}
595	return nil
596}
597
598func (t *TxManager) canForgeAt(blockNum int64) bool {
599	return canForge(&t.consts.Auction, &t.vars.Auction,
600		&t.stats.Sync.Auction.CurrentSlot, &t.stats.Sync.Auction.NextSlot,
601		t.cfg.ForgerAddress, blockNum)
602}
603
604func (t *TxManager) mustL1L2Batch(blockNum int64) bool {
605	lastL1BatchBlockNum := t.lastSentL1BatchBlockNum
606	if t.stats.Sync.LastL1BatchBlock > lastL1BatchBlockNum {
607		lastL1BatchBlockNum = t.stats.Sync.LastL1BatchBlock
608	}
609	return blockNum-lastL1BatchBlockNum >= t.vars.Rollup.ForgeL1L2BatchTimeout-1
610}