hermez-node/synchronizer/synchronizer.go
synchronizer/synchronizer.goRaw
1package synchronizer
2
3import (
4 "context"
5 "database/sql"
6 "fmt"
7 "math/big"
8 "sync"
9 "time"
10
11 "github.com/ethereum/go-ethereum"
12 "github.com/hermeznetwork/hermez-node/common"
13 "github.com/hermeznetwork/hermez-node/db/historydb"
14 "github.com/hermeznetwork/hermez-node/db/statedb"
15 "github.com/hermeznetwork/hermez-node/eth"
16 "github.com/hermeznetwork/hermez-node/log"
17 "github.com/hermeznetwork/hermez-node/txprocessor"
18 "github.com/hermeznetwork/tracerr"
19)
20
21// Stats of the syncrhonizer
22type Stats struct {
23 Eth struct {
24 RefreshPeriod time.Duration
25 Updated time.Time
26 FirstBlockNum int64
27 LastBlock common.Block
28 LastBatch int64
29 }
30 Sync struct {
31 Updated time.Time
32 LastBlock common.Block
33 LastBatch int64
34 // LastL1BatchBlock is the last ethereum block in which an
35 // l1Batch was forged
36 LastL1BatchBlock int64
37 LastForgeL1TxsNum int64
38 Auction struct {
39 CurrentSlot common.Slot
40 }
41 }
42}
43
44// Synced returns true if the Synchronizer is up to date with the last ethereum block
45func (s *Stats) Synced() bool {
46 return s.Eth.LastBlock.Num == s.Sync.LastBlock.Num
47}
48
49// TODO(Edu): Consider removing all the mutexes from StatsHolder, make
50// Synchronizer.Stats not thread-safe, don't pass the synchronizer to the
51// debugAPI, and have a copy of the Stats in the DebugAPI that the node passes
52// when the Sync updates.
53
54// StatsHolder stores stats and that allows reading and writing them
55// concurrently
56type StatsHolder struct {
57 Stats
58 rw sync.RWMutex
59}
60
61// NewStatsHolder creates a new StatsHolder
62func NewStatsHolder(firstBlockNum int64, refreshPeriod time.Duration) *StatsHolder {
63 stats := Stats{}
64 stats.Eth.RefreshPeriod = refreshPeriod
65 stats.Eth.FirstBlockNum = firstBlockNum
66 stats.Sync.LastForgeL1TxsNum = -1
67 return &StatsHolder{Stats: stats}
68}
69
70// UpdateCurrentSlot updates the auction stats
71func (s *StatsHolder) UpdateCurrentSlot(slot common.Slot) {
72 s.rw.Lock()
73 s.Sync.Auction.CurrentSlot = slot
74 s.rw.Unlock()
75}
76
77// UpdateSync updates the synchronizer stats
78func (s *StatsHolder) UpdateSync(lastBlock *common.Block, lastBatch *common.BatchNum,
79 lastL1BatchBlock *int64, lastForgeL1TxsNum *int64) {
80 now := time.Now()
81 s.rw.Lock()
82 s.Sync.LastBlock = *lastBlock
83 if lastBatch != nil {
84 s.Sync.LastBatch = int64(*lastBatch)
85 }
86 if lastL1BatchBlock != nil {
87 s.Sync.LastL1BatchBlock = *lastL1BatchBlock
88 s.Sync.LastForgeL1TxsNum = *lastForgeL1TxsNum
89 }
90 s.Sync.Updated = now
91 s.rw.Unlock()
92}
93
94// UpdateEth updates the ethereum stats, only if the previous stats expired
95func (s *StatsHolder) UpdateEth(ethClient eth.ClientInterface) error {
96 now := time.Now()
97 s.rw.RLock()
98 elapsed := now.Sub(s.Eth.Updated)
99 s.rw.RUnlock()
100 if elapsed < s.Eth.RefreshPeriod {
101 return nil
102 }
103
104 lastBlock, err := ethClient.EthBlockByNumber(context.TODO(), -1)
105 if err != nil {
106 return tracerr.Wrap(err)
107 }
108 lastBatch, err := ethClient.RollupLastForgedBatch()
109 if err != nil {
110 return tracerr.Wrap(err)
111 }
112 s.rw.Lock()
113 s.Eth.Updated = now
114 s.Eth.LastBlock = *lastBlock
115 s.Eth.LastBatch = lastBatch
116 s.rw.Unlock()
117 return nil
118}
119
120// CopyStats returns a copy of the inner Stats
121func (s *StatsHolder) CopyStats() *Stats {
122 s.rw.RLock()
123 sCopy := s.Stats
124 if s.Sync.Auction.CurrentSlot.BidValue != nil {
125 sCopy.Sync.Auction.CurrentSlot.BidValue =
126 common.CopyBigInt(s.Sync.Auction.CurrentSlot.BidValue)
127 }
128 if s.Sync.Auction.CurrentSlot.DefaultSlotBid != nil {
129 sCopy.Sync.Auction.CurrentSlot.DefaultSlotBid =
130 common.CopyBigInt(s.Sync.Auction.CurrentSlot.DefaultSlotBid)
131 }
132 s.rw.RUnlock()
133 return &sCopy
134}
135
136func (s *StatsHolder) blocksPerc() float64 {
137 syncLastBlockNum := s.Sync.LastBlock.Num
138 if s.Sync.LastBlock.Num == 0 {
139 syncLastBlockNum = s.Eth.FirstBlockNum - 1
140 }
141 return float64(syncLastBlockNum-(s.Eth.FirstBlockNum-1)) * 100.0 /
142 float64(s.Eth.LastBlock.Num-(s.Eth.FirstBlockNum-1))
143}
144
145func (s *StatsHolder) batchesPerc(batchNum int64) float64 {
146 return float64(batchNum) * 100.0 /
147 float64(s.Eth.LastBatch)
148}
149
150// StartBlockNums sets the first block used to start tracking the smart
151// contracts
152type StartBlockNums struct {
153 Rollup int64
154 Auction int64
155 WDelayer int64
156}
157
158// SCVariables joins all the smart contract variables in a single struct
159type SCVariables struct {
160 Rollup common.RollupVariables `validate:"required"`
161 Auction common.AuctionVariables `validate:"required"`
162 WDelayer common.WDelayerVariables `validate:"required"`
163}
164
165// SCVariablesPtr joins all the smart contract variables as pointers in a single
166// struct
167type SCVariablesPtr struct {
168 Rollup *common.RollupVariables `validate:"required"`
169 Auction *common.AuctionVariables `validate:"required"`
170 WDelayer *common.WDelayerVariables `validate:"required"`
171}
172
173// SCConsts joins all the smart contract constants in a single struct
174type SCConsts struct {
175 Rollup common.RollupConstants
176 Auction common.AuctionConstants
177 WDelayer common.WDelayerConstants
178}
179
180// Config is the Synchronizer configuration
181type Config struct {
182 StatsRefreshPeriod time.Duration
183}
184
185// Synchronizer implements the Synchronizer type
186type Synchronizer struct {
187 ethClient eth.ClientInterface
188 consts SCConsts
189 historyDB *historydb.HistoryDB
190 stateDB *statedb.StateDB
191 cfg Config
192 initVars SCVariables
193 startBlockNum int64
194 vars SCVariables
195 stats *StatsHolder
196}
197
198// NewSynchronizer creates a new Synchronizer
199func NewSynchronizer(ethClient eth.ClientInterface, historyDB *historydb.HistoryDB,
200 stateDB *statedb.StateDB, cfg Config) (*Synchronizer, error) {
201 auctionConstants, err := ethClient.AuctionConstants()
202 if err != nil {
203 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.AuctionConstants(): %w",
204 err))
205 }
206 rollupConstants, err := ethClient.RollupConstants()
207 if err != nil {
208 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.RollupConstants(): %w",
209 err))
210 }
211 wDelayerConstants, err := ethClient.WDelayerConstants()
212 if err != nil {
213 return nil, tracerr.Wrap(fmt.Errorf("NewSynchronizer ethClient.WDelayerConstants(): %w",
214 err))
215 }
216 consts := SCConsts{
217 Rollup: *rollupConstants,
218 Auction: *auctionConstants,
219 WDelayer: *wDelayerConstants,
220 }
221
222 initVars, startBlockNums, err := getInitialVariables(ethClient, &consts)
223 if err != nil {
224 return nil, tracerr.Wrap(err)
225 }
226 log.Infow("Synchronizer syncing from smart contract blocks",
227 "rollup", startBlockNums.Rollup,
228 "auction", startBlockNums.Auction,
229 "wdelayer", startBlockNums.WDelayer,
230 )
231 // Set startBlockNum to the minimum between Auction, Rollup and
232 // WDelayer StartBlockNum
233 startBlockNum := startBlockNums.Auction
234 if startBlockNums.Rollup < startBlockNum {
235 startBlockNum = startBlockNums.Rollup
236 }
237 if startBlockNums.WDelayer < startBlockNum {
238 startBlockNum = startBlockNums.WDelayer
239 }
240 stats := NewStatsHolder(startBlockNum, cfg.StatsRefreshPeriod)
241 s := &Synchronizer{
242 ethClient: ethClient,
243 consts: consts,
244 historyDB: historyDB,
245 stateDB: stateDB,
246 cfg: cfg,
247 initVars: *initVars,
248 startBlockNum: startBlockNum,
249 stats: stats,
250 }
251 return s, s.init()
252}
253
254// Stats returns a copy of the Synchronizer Stats. It is safe to call Stats()
255// during a Sync call
256func (s *Synchronizer) Stats() *Stats {
257 return s.stats.CopyStats()
258}
259
260// AuctionConstants returns the AuctionConstants read from the smart contract
261func (s *Synchronizer) AuctionConstants() *common.AuctionConstants {
262 return &s.consts.Auction
263}
264
265// RollupConstants returns the RollupConstants read from the smart contract
266func (s *Synchronizer) RollupConstants() *common.RollupConstants {
267 return &s.consts.Rollup
268}
269
270// WDelayerConstants returns the WDelayerConstants read from the smart contract
271func (s *Synchronizer) WDelayerConstants() *common.WDelayerConstants {
272 return &s.consts.WDelayer
273}
274
275// SCVars returns a copy of the Smart Contract Variables
276func (s *Synchronizer) SCVars() SCVariablesPtr {
277 return SCVariablesPtr{
278 Rollup: s.vars.Rollup.Copy(),
279 Auction: s.vars.Auction.Copy(),
280 WDelayer: s.vars.WDelayer.Copy(),
281 }
282}
283
284// firstBatchBlockNum is the blockNum of first batch in that block, if any
285func (s *Synchronizer) updateCurrentSlotIfSync(reset bool, firstBatchBlockNum *int64) error {
286 slot := common.Slot{
287 SlotNum: s.stats.Sync.Auction.CurrentSlot.SlotNum,
288 ForgerCommitment: s.stats.Sync.Auction.CurrentSlot.ForgerCommitment,
289 }
290 // We want the next block because the current one is already mined
291 blockNum := s.stats.Sync.LastBlock.Num + 1
292 slotNum := s.consts.Auction.SlotNum(blockNum)
293 if reset {
294 dbFirstBatchBlockNum, err := s.historyDB.GetFirstBatchBlockNumBySlot(slotNum)
295 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
296 return tracerr.Wrap(fmt.Errorf("historyDB.GetFirstBatchBySlot: %w", err))
297 } else if tracerr.Unwrap(err) == sql.ErrNoRows {
298 firstBatchBlockNum = nil
299 } else {
300 firstBatchBlockNum = &dbFirstBatchBlockNum
301 }
302 slot.ForgerCommitment = false
303 } else if slotNum > slot.SlotNum {
304 // We are in a new slotNum, start from default values
305 slot.ForgerCommitment = false
306 }
307 slot.SlotNum = slotNum
308 slot.StartBlock, slot.EndBlock = s.consts.Auction.SlotBlocks(slot.SlotNum)
309 // If Synced, update the current coordinator
310 if s.stats.Synced() && blockNum >= s.consts.Auction.GenesisBlockNum {
311 bidCoord, err := s.historyDB.GetBestBidCoordinator(slot.SlotNum)
312 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
313 return tracerr.Wrap(err)
314 }
315 if tracerr.Unwrap(err) == sql.ErrNoRows {
316 slot.BootCoord = true
317 slot.Forger = s.vars.Auction.BootCoordinator
318 slot.URL = s.vars.Auction.BootCoordinatorURL
319 } else if err == nil {
320 slot.BidValue = bidCoord.BidValue
321 slot.DefaultSlotBid = bidCoord.DefaultSlotSetBid[slot.SlotNum%6]
322 // Only if the highest bid value is greater/equal than
323 // the default slot bid, the bidder is the winner of
324 // the slot. Otherwise the boot coordinator is the
325 // winner.
326 if slot.BidValue.Cmp(slot.DefaultSlotBid) >= 0 {
327 slot.Bidder = bidCoord.Bidder
328 slot.Forger = bidCoord.Forger
329 slot.URL = bidCoord.URL
330 } else {
331 slot.BootCoord = true
332 slot.Forger = s.vars.Auction.BootCoordinator
333 slot.URL = s.vars.Auction.BootCoordinatorURL
334 }
335 }
336 if firstBatchBlockNum != nil &&
337 s.consts.Auction.RelativeBlock(*firstBatchBlockNum) <
338 int64(s.vars.Auction.SlotDeadline) {
339 slot.ForgerCommitment = true
340 }
341
342 // TODO: Remove this SANITY CHECK once this code is tested enough
343 // BEGIN SANITY CHECK
344 canForge, err := s.ethClient.AuctionCanForge(slot.Forger, blockNum)
345 if err != nil {
346 return tracerr.Wrap(err)
347 }
348 if !canForge {
349 return tracerr.Wrap(fmt.Errorf("Synchronized value of forger address for closed slot "+
350 "differs from smart contract: %+v", slot))
351 }
352 // END SANITY CHECK
353 }
354 s.stats.UpdateCurrentSlot(slot)
355 return nil
356}
357
358func (s *Synchronizer) init() error {
359 // Update stats parameters so that they have valid values before the
360 // first Sync call
361 if err := s.stats.UpdateEth(s.ethClient); err != nil {
362 return tracerr.Wrap(err)
363 }
364 lastBlock := &common.Block{}
365 lastSavedBlock, err := s.historyDB.GetLastBlock()
366 // `s.historyDB.GetLastBlock()` will never return `sql.ErrNoRows`
367 // because we always have the default block 0 in the DB
368 if err != nil {
369 return tracerr.Wrap(err)
370 }
371 // If we only have the default block 0,
372 // make sure that the stateDB is clean
373 if lastSavedBlock.Num == 0 {
374 if err := s.stateDB.Reset(0); err != nil {
375 return tracerr.Wrap(err)
376 }
377 } else {
378 lastBlock = lastSavedBlock
379 }
380 if err := s.resetState(lastBlock); err != nil {
381 return tracerr.Wrap(err)
382 }
383
384 log.Infow("Sync init block",
385 "syncLastBlock", s.stats.Sync.LastBlock,
386 "syncBlocksPerc", s.stats.blocksPerc(),
387 "ethFirstBlockNum", s.stats.Eth.FirstBlockNum,
388 "ethLastBlock", s.stats.Eth.LastBlock,
389 )
390 log.Infow("Sync init batch",
391 "syncLastBatch", s.stats.Sync.LastBatch,
392 "syncBatchesPerc", s.stats.batchesPerc(s.stats.Sync.LastBatch),
393 "ethLastBatch", s.stats.Eth.LastBatch,
394 )
395 return nil
396}
397
398// Sync2 attems to synchronize an ethereum block starting from lastSavedBlock.
399// If lastSavedBlock is nil, the lastSavedBlock value is obtained from de DB.
400// If a block is synched, it will be returned and also stored in the DB. If a
401// reorg is detected, the number of discarded blocks will be returned and no
402// synchronization will be made.
403// TODO: Be smart about locking: only lock during the read/write operations
404func (s *Synchronizer) Sync2(ctx context.Context, lastSavedBlock *common.Block) (*common.BlockData, *int64, error) {
405 var nextBlockNum int64 // next block number to sync
406 if lastSavedBlock == nil {
407 var err error
408 // Get lastSavedBlock from History DB
409 lastSavedBlock, err = s.historyDB.GetLastBlock()
410 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
411 return nil, nil, tracerr.Wrap(err)
412 }
413 // If we don't have any stored block, we must do a full sync
414 // starting from the startBlockNum
415 if tracerr.Unwrap(err) == sql.ErrNoRows || lastSavedBlock.Num == 0 {
416 nextBlockNum = s.startBlockNum
417 lastSavedBlock = nil
418 }
419 }
420 if lastSavedBlock != nil {
421 nextBlockNum = lastSavedBlock.Num + 1
422 if lastSavedBlock.Num < s.startBlockNum {
423 return nil, nil, tracerr.Wrap(
424 fmt.Errorf("lastSavedBlock (%v) < startBlockNum (%v)",
425 lastSavedBlock.Num, s.startBlockNum))
426 }
427 }
428
429 ethBlock, err := s.ethClient.EthBlockByNumber(ctx, nextBlockNum)
430 if tracerr.Unwrap(err) == ethereum.NotFound {
431 return nil, nil, nil
432 } else if err != nil {
433 return nil, nil, tracerr.Wrap(err)
434 }
435 log.Debugf("ethBlock: num: %v, parent: %v, hash: %v",
436 ethBlock.Num, ethBlock.ParentHash.String(), ethBlock.Hash.String())
437
438 if err := s.stats.UpdateEth(s.ethClient); err != nil {
439 return nil, nil, tracerr.Wrap(err)
440 }
441
442 log.Debugw("Syncing...",
443 "block", nextBlockNum,
444 "ethLastBlock", s.stats.Eth.LastBlock,
445 )
446
447 // Check that the obtianed ethBlock.ParentHash == prevEthBlock.Hash; if not, reorg!
448 if lastSavedBlock != nil {
449 if lastSavedBlock.Hash != ethBlock.ParentHash {
450 // Reorg detected
451 log.Debugw("Reorg Detected",
452 "blockNum", ethBlock.Num,
453 "block.parent(got)", ethBlock.ParentHash, "parent.hash(exp)", lastSavedBlock.Hash)
454 lastDBBlockNum, err := s.reorg(lastSavedBlock)
455 if err != nil {
456 return nil, nil, tracerr.Wrap(err)
457 }
458 discarded := lastSavedBlock.Num - lastDBBlockNum
459 return nil, &discarded, nil
460 }
461 }
462
463 // Get data from the rollup contract
464 rollupData, err := s.rollupSync(ethBlock)
465 if err != nil {
466 return nil, nil, tracerr.Wrap(err)
467 }
468
469 // Get data from the auction contract
470 auctionData, err := s.auctionSync(ethBlock)
471 if err != nil {
472 return nil, nil, tracerr.Wrap(err)
473 }
474
475 // Get data from the WithdrawalDelayer contract
476 wDelayerData, err := s.wdelayerSync(ethBlock)
477 if err != nil {
478 return nil, nil, tracerr.Wrap(err)
479 }
480
481 for i := range rollupData.Withdrawals {
482 withdrawal := &rollupData.Withdrawals[i]
483 if !withdrawal.InstantWithdraw {
484 wDelayerTransfers := wDelayerData.DepositsByTxHash[withdrawal.TxHash]
485 if len(wDelayerTransfers) == 0 {
486 return nil, nil, tracerr.Wrap(fmt.Errorf("WDelayer deposit corresponding to " +
487 "non-instant rollup withdrawal not found"))
488 }
489 // Pop the first wDelayerTransfer to consume them in chronological order
490 wDelayerTransfer := wDelayerTransfers[0]
491 wDelayerData.DepositsByTxHash[withdrawal.TxHash] =
492 wDelayerData.DepositsByTxHash[withdrawal.TxHash][1:]
493
494 withdrawal.Owner = wDelayerTransfer.Owner
495 withdrawal.Token = wDelayerTransfer.Token
496 }
497 }
498
499 // Group all the block data into the structs to save into HistoryDB
500 blockData := common.BlockData{
501 Block: *ethBlock,
502 Rollup: *rollupData,
503 Auction: *auctionData,
504 WDelayer: *wDelayerData,
505 }
506
507 err = s.historyDB.AddBlockSCData(&blockData)
508 if err != nil {
509 return nil, nil, tracerr.Wrap(err)
510 }
511
512 batchesLen := len(rollupData.Batches)
513 if batchesLen == 0 {
514 s.stats.UpdateSync(ethBlock, nil, nil, nil)
515 } else {
516 var lastL1BatchBlock *int64
517 var lastForgeL1TxsNum *int64
518 for _, batchData := range rollupData.Batches {
519 if batchData.L1Batch {
520 lastL1BatchBlock = &batchData.Batch.EthBlockNum
521 lastForgeL1TxsNum = batchData.Batch.ForgeL1TxsNum
522 }
523 }
524 s.stats.UpdateSync(ethBlock,
525 &rollupData.Batches[batchesLen-1].Batch.BatchNum,
526 lastL1BatchBlock, lastForgeL1TxsNum)
527 }
528 var firstBatchBlockNum *int64
529 if len(rollupData.Batches) > 0 {
530 firstBatchBlockNum = &rollupData.Batches[0].Batch.EthBlockNum
531 }
532 if err := s.updateCurrentSlotIfSync(false, firstBatchBlockNum); err != nil {
533 return nil, nil, tracerr.Wrap(err)
534 }
535
536 log.Debugw("Synced block",
537 "syncLastBlockNum", s.stats.Sync.LastBlock.Num,
538 "syncBlocksPerc", s.stats.blocksPerc(),
539 "ethLastBlockNum", s.stats.Eth.LastBlock.Num,
540 )
541 for _, batchData := range rollupData.Batches {
542 log.Debugw("Synced batch",
543 "syncLastBatch", batchData.Batch.BatchNum,
544 "syncBatchesPerc", s.stats.batchesPerc(int64(batchData.Batch.BatchNum)),
545 "ethLastBatch", s.stats.Eth.LastBatch,
546 )
547 }
548
549 return &blockData, nil, nil
550}
551
552// reorg manages a reorg, updating History and State DB as needed. Keeps
553// checking previous blocks from the HistoryDB against the blockchain until a
554// block hash match is found. All future blocks in the HistoryDB and
555// corresponding batches in StateBD are discarded. Returns the last valid
556// blockNum from the HistoryDB.
557func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) {
558 blockNum := uncleBlock.Num
559
560 var block *common.Block
561 for blockNum >= s.startBlockNum {
562 ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum)
563 if err != nil {
564 return 0, tracerr.Wrap(fmt.Errorf("ethClient.EthBlockByNumber: %w", err))
565 }
566
567 block, err = s.historyDB.GetBlock(blockNum)
568 if err != nil {
569 return 0, tracerr.Wrap(fmt.Errorf("historyDB.GetBlock: %w", err))
570 }
571 if block.Hash == ethBlock.Hash {
572 log.Debugf("Found valid block: %v", blockNum)
573 break
574 }
575 blockNum--
576 }
577 total := uncleBlock.Num - block.Num
578 log.Debugw("Discarding blocks", "total", total, "from", uncleBlock.Num, "to", block.Num+1)
579
580 // Set History DB and State DB to the correct state
581 err := s.historyDB.Reorg(block.Num)
582 if err != nil {
583 return 0, tracerr.Wrap(err)
584 }
585
586 if err := s.resetState(block); err != nil {
587 return 0, tracerr.Wrap(err)
588 }
589
590 return block.Num, nil
591}
592
593func getInitialVariables(ethClient eth.ClientInterface,
594 consts *SCConsts) (*SCVariables, *StartBlockNums, error) {
595 rollupInit, rollupInitBlock, err := ethClient.RollupEventInit()
596 if err != nil {
597 return nil, nil, tracerr.Wrap(err)
598 }
599 auctionInit, auctionInitBlock, err := ethClient.AuctionEventInit()
600 if err != nil {
601 return nil, nil, tracerr.Wrap(err)
602 }
603 wDelayerInit, wDelayerInitBlock, err := ethClient.WDelayerEventInit()
604 if err != nil {
605 return nil, nil, tracerr.Wrap(err)
606 }
607 rollupVars := rollupInit.RollupVariables()
608 auctionVars := auctionInit.AuctionVariables(consts.Auction.InitialMinimalBidding)
609 wDelayerVars := wDelayerInit.WDelayerVariables()
610 return &SCVariables{
611 Rollup: *rollupVars,
612 Auction: *auctionVars,
613 WDelayer: *wDelayerVars,
614 }, &StartBlockNums{
615 Rollup: rollupInitBlock,
616 Auction: auctionInitBlock,
617 WDelayer: wDelayerInitBlock,
618 }, nil
619}
620
621func (s *Synchronizer) resetState(block *common.Block) error {
622 rollup, auction, wDelayer, err := s.historyDB.GetSCVars()
623 // If SCVars are not in the HistoryDB, this is probably the first run
624 // of the Synchronizer: store the initial vars taken from config
625 if tracerr.Unwrap(err) == sql.ErrNoRows {
626 vars := s.initVars
627 log.Info("Setting initial SCVars in HistoryDB")
628 if err = s.historyDB.SetInitialSCVars(&vars.Rollup, &vars.Auction, &vars.WDelayer); err != nil {
629 return tracerr.Wrap(fmt.Errorf("historyDB.SetInitialSCVars: %w", err))
630 }
631 s.vars.Rollup = *vars.Rollup.Copy()
632 s.vars.Auction = *vars.Auction.Copy()
633 s.vars.WDelayer = *vars.WDelayer.Copy()
634 // Add initial boot coordinator to HistoryDB
635 if err := s.historyDB.AddCoordinators([]common.Coordinator{{
636 Forger: s.initVars.Auction.BootCoordinator,
637 URL: s.initVars.Auction.BootCoordinatorURL,
638 EthBlockNum: s.initVars.Auction.EthBlockNum,
639 }}); err != nil {
640 return tracerr.Wrap(err)
641 }
642 } else if err != nil {
643 return tracerr.Wrap(err)
644 } else {
645 s.vars.Rollup = *rollup
646 s.vars.Auction = *auction
647 s.vars.WDelayer = *wDelayer
648 }
649
650 batchNum, err := s.historyDB.GetLastBatchNum()
651 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
652 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastBatchNum: %w", err))
653 }
654 if tracerr.Unwrap(err) == sql.ErrNoRows {
655 batchNum = 0
656 }
657
658 lastL1BatchBlockNum, err := s.historyDB.GetLastL1BatchBlockNum()
659 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
660 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastL1BatchBlockNum: %w", err))
661 }
662 if tracerr.Unwrap(err) == sql.ErrNoRows {
663 lastL1BatchBlockNum = 0
664 }
665
666 lastForgeL1TxsNum, err := s.historyDB.GetLastL1TxsNum()
667 if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
668 return tracerr.Wrap(fmt.Errorf("historyDB.GetLastL1BatchBlockNum: %w", err))
669 }
670 if tracerr.Unwrap(err) == sql.ErrNoRows || lastForgeL1TxsNum == nil {
671 n := int64(-1)
672 lastForgeL1TxsNum = &n
673 }
674
675 err = s.stateDB.Reset(batchNum)
676 if err != nil {
677 return tracerr.Wrap(fmt.Errorf("stateDB.Reset: %w", err))
678 }
679
680 s.stats.UpdateSync(block, &batchNum, &lastL1BatchBlockNum, lastForgeL1TxsNum)
681
682 if err := s.updateCurrentSlotIfSync(true, nil); err != nil {
683 return tracerr.Wrap(err)
684 }
685 return nil
686}
687
688// rollupSync retreives all the Rollup Smart Contract Data that happened at
689// ethBlock.blockNum with ethBlock.Hash.
690func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*common.RollupData, error) {
691 blockNum := ethBlock.Num
692 var rollupData = common.NewRollupData()
693 // var forgeL1TxsNum int64
694
695 // Get rollup events in the block, and make sure the block hash matches
696 // the expected one.
697 rollupEvents, blockHash, err := s.ethClient.RollupEventsByBlock(blockNum)
698 if err != nil {
699 return nil, tracerr.Wrap(err)
700 }
701 // No events in this block
702 if blockHash == nil {
703 return &rollupData, nil
704 }
705 if *blockHash != ethBlock.Hash {
706 log.Errorw("Block hash mismatch in Rollup events", "expected", ethBlock.Hash.String(),
707 "got", blockHash.String())
708 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
709 }
710
711 var nextForgeL1TxsNum int64 // forgeL1TxsNum for the next L1Batch
712 nextForgeL1TxsNumPtr, err := s.historyDB.GetLastL1TxsNum()
713 if err != nil {
714 return nil, tracerr.Wrap(err)
715 }
716 if nextForgeL1TxsNumPtr != nil {
717 nextForgeL1TxsNum = *nextForgeL1TxsNumPtr + 1
718 } else {
719 nextForgeL1TxsNum = 0
720 }
721
722 // Get L1UserTX
723 rollupData.L1UserTxs, err = getL1UserTx(rollupEvents.L1UserTx, blockNum)
724 if err != nil {
725 return nil, tracerr.Wrap(err)
726 }
727
728 // Get ForgeBatch events to get the L1CoordinatorTxs
729 for _, evtForgeBatch := range rollupEvents.ForgeBatch {
730 batchData := common.NewBatchData()
731 position := 0
732
733 // Get the input for each Tx
734 forgeBatchArgs, sender, err := s.ethClient.RollupForgeBatchArgs(evtForgeBatch.EthTxHash,
735 evtForgeBatch.L1UserTxsLen)
736 if err != nil {
737 return nil, tracerr.Wrap(err)
738 }
739
740 batchNum := common.BatchNum(evtForgeBatch.BatchNum)
741 var l1UserTxs []common.L1Tx
742 // Check if this is a L1Batch to get L1 Tx from it
743 if forgeBatchArgs.L1Batch {
744 // Get L1UserTxs with toForgeL1TxsNum, which correspond
745 // to the L1UserTxs that are forged in this batch, so
746 // that stateDB can process them.
747
748 // First try to find them in HistoryDB.
749 l1UserTxs, err = s.historyDB.GetUnforgedL1UserTxs(nextForgeL1TxsNum)
750 if err != nil {
751 return nil, tracerr.Wrap(err)
752 }
753 // Apart from the DB, try to find them in this block.
754 // This could happen because in a block there could be
755 // multiple batches with L1Batch = true (although it's
756 // a very rare case). If not found in the DB and the
757 // block doesn't contain the l1UserTxs, it means that
758 // the L1UserTxs queue with toForgeL1TxsNum was closed
759 // empty, so we leave `l1UserTxs` as an empty slice.
760 for _, l1UserTx := range rollupData.L1UserTxs {
761 if *l1UserTx.ToForgeL1TxsNum == nextForgeL1TxsNum {
762 l1UserTxs = append(l1UserTxs, l1UserTx)
763 }
764 }
765
766 position = len(l1UserTxs)
767 }
768 // Get L1 Coordinator Txs
769 for i := range forgeBatchArgs.L1CoordinatorTxs {
770 l1CoordinatorTx := forgeBatchArgs.L1CoordinatorTxs[i]
771 l1CoordinatorTx.Position = position
772 // l1CoordinatorTx.ToForgeL1TxsNum = &forgeL1TxsNum
773 l1CoordinatorTx.UserOrigin = false
774 l1CoordinatorTx.EthBlockNum = blockNum
775 l1CoordinatorTx.BatchNum = &batchNum
776 l1Tx, err := common.NewL1Tx(&l1CoordinatorTx)
777 if err != nil {
778 return nil, tracerr.Wrap(err)
779 }
780
781 batchData.L1CoordinatorTxs = append(batchData.L1CoordinatorTxs, *l1Tx)
782 position++
783 // fmt.Println("DGB l1coordtx")
784 }
785
786 // Insert all the txs forged in this batch (l1UserTxs,
787 // L1CoordinatorTxs, PoolL2Txs) into stateDB so that they are
788 // processed.
789
790 // Set TxType to the forged L2Txs
791 for i := range forgeBatchArgs.L2TxsData {
792 if err := forgeBatchArgs.L2TxsData[i].SetType(); err != nil {
793 return nil, tracerr.Wrap(err)
794 }
795 }
796
797 // Transform L2 txs to PoolL2Txs
798 poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // NOTE: This is a big ugly, find a better way
799
800 // ProcessTxs updates poolL2Txs adding: Nonce (and also TokenID, but we don't use it).
801 //nolint:gomnd
802 tpc := txprocessor.Config{ // TODO TMP
803 NLevels: 32,
804 MaxFeeTx: 64,
805 MaxTx: 512,
806 MaxL1Tx: 64,
807 ChainID: uint16(0),
808 }
809 tp := txprocessor.NewTxProcessor(s.stateDB, tpc)
810
811 processTxsOut, err := tp.ProcessTxs(forgeBatchArgs.FeeIdxCoordinator,
812 l1UserTxs, batchData.L1CoordinatorTxs, poolL2Txs)
813 if err != nil {
814 return nil, tracerr.Wrap(err)
815 }
816
817 // Transform processed PoolL2 txs to L2 and store in BatchData
818 l2Txs, err := common.PoolL2TxsToL2Txs(poolL2Txs) // NOTE: This is a big uggly, find a better way
819 if err != nil {
820 return nil, tracerr.Wrap(err)
821 }
822
823 // Set TxID, BlockNum, BatchNum and Position to the forged L2Txs
824 for i := range l2Txs {
825 if err := l2Txs[i].SetID(); err != nil {
826 return nil, tracerr.Wrap(err)
827 }
828 l2Txs[i].EthBlockNum = blockNum
829 l2Txs[i].BatchNum = batchNum
830 l2Txs[i].Position = position
831 position++
832 }
833 batchData.L2Txs = l2Txs
834
835 // Set the BatchNum in the forged L1UserTxs
836 for i := range l1UserTxs {
837 l1UserTxs[i].BatchNum = &batchNum
838 }
839 batchData.L1UserTxs = l1UserTxs
840
841 // Set batchNum in exits
842 for i := range processTxsOut.ExitInfos {
843 exit := &processTxsOut.ExitInfos[i]
844 exit.BatchNum = batchNum
845 }
846 batchData.ExitTree = processTxsOut.ExitInfos
847
848 for i := range processTxsOut.CreatedAccounts {
849 createdAccount := &processTxsOut.CreatedAccounts[i]
850 createdAccount.Nonce = 0
851 createdAccount.Balance = big.NewInt(0)
852 createdAccount.BatchNum = batchNum
853 }
854 batchData.CreatedAccounts = processTxsOut.CreatedAccounts
855
856 slotNum := int64(0)
857 if ethBlock.Num >= s.consts.Auction.GenesisBlockNum {
858 slotNum = (ethBlock.Num - s.consts.Auction.GenesisBlockNum) /
859 int64(s.consts.Auction.BlocksPerSlot)
860 }
861
862 // Get Batch information
863 batch := common.Batch{
864 BatchNum: batchNum,
865 EthBlockNum: blockNum,
866 ForgerAddr: *sender,
867 CollectedFees: processTxsOut.CollectedFees,
868 FeeIdxsCoordinator: forgeBatchArgs.FeeIdxCoordinator,
869 StateRoot: forgeBatchArgs.NewStRoot,
870 NumAccounts: len(batchData.CreatedAccounts),
871 LastIdx: forgeBatchArgs.NewLastIdx,
872 ExitRoot: forgeBatchArgs.NewExitRoot,
873 SlotNum: slotNum,
874 }
875 nextForgeL1TxsNumCpy := nextForgeL1TxsNum
876 if forgeBatchArgs.L1Batch {
877 batch.ForgeL1TxsNum = &nextForgeL1TxsNumCpy
878 batchData.L1Batch = true
879 nextForgeL1TxsNum++
880 }
881 batchData.Batch = batch
882 rollupData.Batches = append(rollupData.Batches, *batchData)
883 }
884
885 // Get Registered Tokens
886 for _, evtAddToken := range rollupEvents.AddToken {
887 var token common.Token
888
889 token.TokenID = common.TokenID(evtAddToken.TokenID)
890 token.EthAddr = evtAddToken.TokenAddress
891 token.EthBlockNum = blockNum
892
893 if consts, err := s.ethClient.EthERC20Consts(evtAddToken.TokenAddress); err != nil {
894 log.Warnw("Error retreiving ERC20 token constants", "addr", evtAddToken.TokenAddress)
895 token.Name = "ERC20_ETH_ERROR"
896 token.Symbol = "ERROR"
897 token.Decimals = 1
898 } else {
899 token.Name = cutStringMax(consts.Name, 20)
900 token.Symbol = cutStringMax(consts.Symbol, 10)
901 token.Decimals = consts.Decimals
902 }
903
904 rollupData.AddedTokens = append(rollupData.AddedTokens, token)
905 }
906
907 for _, evt := range rollupEvents.UpdateBucketWithdraw {
908 rollupData.UpdateBucketWithdraw = append(rollupData.UpdateBucketWithdraw,
909 common.BucketUpdate{
910 EthBlockNum: blockNum,
911 NumBucket: evt.NumBucket,
912 BlockStamp: evt.BlockStamp,
913 Withdrawals: evt.Withdrawals,
914 })
915 }
916
917 for _, evt := range rollupEvents.Withdraw {
918 rollupData.Withdrawals = append(rollupData.Withdrawals, common.WithdrawInfo{
919 Idx: common.Idx(evt.Idx),
920 NumExitRoot: common.BatchNum(evt.NumExitRoot),
921 InstantWithdraw: evt.InstantWithdraw,
922 TxHash: evt.TxHash,
923 })
924 }
925
926 for _, evt := range rollupEvents.UpdateTokenExchange {
927 if len(evt.AddressArray) != len(evt.ValueArray) {
928 return nil, tracerr.Wrap(fmt.Errorf("in RollupEventUpdateTokenExchange "+
929 "len(AddressArray) != len(ValueArray) (%v != %v)",
930 len(evt.AddressArray), len(evt.ValueArray)))
931 }
932 for i := range evt.AddressArray {
933 rollupData.TokenExchanges = append(rollupData.TokenExchanges,
934 common.TokenExchange{
935 EthBlockNum: blockNum,
936 Address: evt.AddressArray[i],
937 ValueUSD: int64(evt.ValueArray[i]),
938 })
939 }
940 }
941
942 varsUpdate := false
943
944 for _, evt := range rollupEvents.UpdateForgeL1L2BatchTimeout {
945 s.vars.Rollup.ForgeL1L2BatchTimeout = evt.NewForgeL1L2BatchTimeout
946 varsUpdate = true
947 }
948
949 for _, evt := range rollupEvents.UpdateFeeAddToken {
950 s.vars.Rollup.FeeAddToken = evt.NewFeeAddToken
951 varsUpdate = true
952 }
953
954 for _, evt := range rollupEvents.UpdateWithdrawalDelay {
955 s.vars.Rollup.WithdrawalDelay = evt.NewWithdrawalDelay
956 varsUpdate = true
957 }
958
959 // NOTE: We skip the event rollupEvents.SafeMode because the
960 // implementation RollupEventsByBlock already inserts a non-existing
961 // RollupEventUpdateBucketsParameters into UpdateBucketsParameters with
962 // all the bucket values at 0 and SafeMode = true
963
964 for _, evt := range rollupEvents.UpdateBucketsParameters {
965 for i, bucket := range evt.ArrayBuckets {
966 s.vars.Rollup.Buckets[i] = common.BucketParams{
967 CeilUSD: bucket.CeilUSD,
968 Withdrawals: bucket.Withdrawals,
969 BlockWithdrawalRate: bucket.BlockWithdrawalRate,
970 MaxWithdrawals: bucket.MaxWithdrawals,
971 }
972 }
973 s.vars.Rollup.SafeMode = evt.SafeMode
974 varsUpdate = true
975 }
976
977 if varsUpdate {
978 s.vars.Rollup.EthBlockNum = blockNum
979 rollupData.Vars = s.vars.Rollup.Copy()
980 }
981
982 return &rollupData, nil
983}
984
985func cutStringMax(s string, max int) string {
986 if len(s) > max {
987 return s[:max]
988 }
989 return s
990}
991
992// auctionSync gets information from the Auction Contract
993func (s *Synchronizer) auctionSync(ethBlock *common.Block) (*common.AuctionData, error) {
994 blockNum := ethBlock.Num
995 var auctionData = common.NewAuctionData()
996
997 // Get auction events in the block
998 auctionEvents, blockHash, err := s.ethClient.AuctionEventsByBlock(blockNum)
999 if err != nil {
1000 return nil, tracerr.Wrap(err)
1001 }
1002 // No events in this block
1003 if blockHash == nil {
1004 return &auctionData, nil
1005 }
1006 if *blockHash != ethBlock.Hash {
1007 log.Errorw("Block hash mismatch in Auction events", "expected", ethBlock.Hash.String(),
1008 "got", blockHash.String())
1009 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
1010 }
1011
1012 // Get bids
1013 for _, evt := range auctionEvents.NewBid {
1014 bid := common.Bid{
1015 SlotNum: evt.Slot,
1016 BidValue: evt.BidAmount,
1017 Bidder: evt.Bidder,
1018 EthBlockNum: blockNum,
1019 }
1020 auctionData.Bids = append(auctionData.Bids, bid)
1021 }
1022
1023 // Get Coordinators
1024 for _, evt := range auctionEvents.SetCoordinator {
1025 coordinator := common.Coordinator{
1026 Bidder: evt.BidderAddress,
1027 Forger: evt.ForgerAddress,
1028 URL: evt.CoordinatorURL,
1029 EthBlockNum: blockNum,
1030 }
1031 auctionData.Coordinators = append(auctionData.Coordinators, coordinator)
1032 }
1033
1034 varsUpdate := false
1035
1036 for _, evt := range auctionEvents.NewSlotDeadline {
1037 s.vars.Auction.SlotDeadline = evt.NewSlotDeadline
1038 varsUpdate = true
1039 }
1040 for _, evt := range auctionEvents.NewClosedAuctionSlots {
1041 s.vars.Auction.ClosedAuctionSlots = evt.NewClosedAuctionSlots
1042 varsUpdate = true
1043 }
1044 for _, evt := range auctionEvents.NewOutbidding {
1045 s.vars.Auction.Outbidding = evt.NewOutbidding
1046 varsUpdate = true
1047 }
1048 for _, evt := range auctionEvents.NewDonationAddress {
1049 s.vars.Auction.DonationAddress = evt.NewDonationAddress
1050 varsUpdate = true
1051 }
1052 for _, evt := range auctionEvents.NewBootCoordinator {
1053 s.vars.Auction.BootCoordinator = evt.NewBootCoordinator
1054 s.vars.Auction.BootCoordinatorURL = evt.NewBootCoordinatorURL
1055 varsUpdate = true
1056 // Add new boot coordinator
1057 auctionData.Coordinators = append(auctionData.Coordinators, common.Coordinator{
1058 Forger: evt.NewBootCoordinator,
1059 URL: evt.NewBootCoordinatorURL,
1060 EthBlockNum: blockNum,
1061 })
1062 }
1063 for _, evt := range auctionEvents.NewOpenAuctionSlots {
1064 s.vars.Auction.OpenAuctionSlots = evt.NewOpenAuctionSlots
1065 varsUpdate = true
1066 }
1067 for _, evt := range auctionEvents.NewAllocationRatio {
1068 s.vars.Auction.AllocationRatio = evt.NewAllocationRatio
1069 varsUpdate = true
1070 }
1071 for _, evt := range auctionEvents.NewDefaultSlotSetBid {
1072 if evt.SlotSet > 6 { //nolint:gomnd
1073 return nil, tracerr.Wrap(fmt.Errorf("unexpected SlotSet in "+
1074 "auctionEvents.NewDefaultSlotSetBid: %v", evt.SlotSet))
1075 }
1076 s.vars.Auction.DefaultSlotSetBid[evt.SlotSet] = evt.NewInitialMinBid
1077 s.vars.Auction.DefaultSlotSetBidSlotNum = s.consts.Auction.SlotNum(blockNum) +
1078 int64(s.vars.Auction.ClosedAuctionSlots) + 1
1079 varsUpdate = true
1080 }
1081
1082 // NOTE: We ignore NewForgeAllocated
1083 // NOTE: We ignore NewForge because we're already tracking ForgeBatch event from Rollup
1084 // NOTE: We ignore HEZClaimed
1085
1086 if varsUpdate {
1087 s.vars.Auction.EthBlockNum = blockNum
1088 auctionData.Vars = s.vars.Auction.Copy()
1089 }
1090
1091 return &auctionData, nil
1092}
1093
1094// wdelayerSync gets information from the Withdrawal Delayer Contract
1095func (s *Synchronizer) wdelayerSync(ethBlock *common.Block) (*common.WDelayerData, error) {
1096 blockNum := ethBlock.Num
1097 wDelayerData := common.NewWDelayerData()
1098
1099 // Get wDelayer events in the block
1100 wDelayerEvents, blockHash, err := s.ethClient.WDelayerEventsByBlock(blockNum)
1101 if err != nil {
1102 return nil, tracerr.Wrap(err)
1103 }
1104 // No events in this block
1105 if blockHash == nil {
1106 return &wDelayerData, nil
1107 }
1108 if *blockHash != ethBlock.Hash {
1109 log.Errorw("Block hash mismatch in WDelayer events", "expected", ethBlock.Hash.String(),
1110 "got", blockHash.String())
1111 return nil, tracerr.Wrap(eth.ErrBlockHashMismatchEvent)
1112 }
1113
1114 for _, evt := range wDelayerEvents.Deposit {
1115 wDelayerData.Deposits = append(wDelayerData.Deposits, common.WDelayerTransfer{
1116 Owner: evt.Owner,
1117 Token: evt.Token,
1118 Amount: evt.Amount,
1119 })
1120 wDelayerData.DepositsByTxHash[evt.TxHash] =
1121 append(wDelayerData.DepositsByTxHash[evt.TxHash],
1122 &wDelayerData.Deposits[len(wDelayerData.Deposits)-1])
1123 }
1124 for _, evt := range wDelayerEvents.Withdraw {
1125 wDelayerData.Withdrawals = append(wDelayerData.Withdrawals, common.WDelayerTransfer{
1126 Owner: evt.Owner,
1127 Token: evt.Token,
1128 Amount: evt.Amount,
1129 })
1130 }
1131 for _, evt := range wDelayerEvents.EscapeHatchWithdrawal {
1132 wDelayerData.EscapeHatchWithdrawals = append(wDelayerData.EscapeHatchWithdrawals,
1133 common.WDelayerEscapeHatchWithdrawal{
1134 EthBlockNum: blockNum,
1135 Who: evt.Who,
1136 To: evt.To,
1137 TokenAddr: evt.Token,
1138 Amount: evt.Amount,
1139 })
1140 }
1141
1142 varsUpdate := false
1143
1144 for range wDelayerEvents.EmergencyModeEnabled {
1145 s.vars.WDelayer.EmergencyMode = true
1146 s.vars.WDelayer.EmergencyModeStartingBlock = blockNum
1147 varsUpdate = true
1148 }
1149 for _, evt := range wDelayerEvents.NewWithdrawalDelay {
1150 s.vars.WDelayer.WithdrawalDelay = evt.WithdrawalDelay
1151 varsUpdate = true
1152 }
1153 for _, evt := range wDelayerEvents.NewEmergencyCouncil {
1154 s.vars.WDelayer.EmergencyCouncilAddress = evt.NewEmergencyCouncil
1155 varsUpdate = true
1156 }
1157 for _, evt := range wDelayerEvents.NewHermezGovernanceAddress {
1158 s.vars.WDelayer.HermezGovernanceAddress = evt.NewHermezGovernanceAddress
1159 varsUpdate = true
1160 }
1161
1162 if varsUpdate {
1163 s.vars.WDelayer.EthBlockNum = blockNum
1164 wDelayerData.Vars = s.vars.WDelayer.Copy()
1165 }
1166
1167 return &wDelayerData, nil
1168}
1169
1170func getL1UserTx(eventsL1UserTx []eth.RollupEventL1UserTx, blockNum int64) ([]common.L1Tx, error) {
1171 l1Txs := make([]common.L1Tx, len(eventsL1UserTx))
1172 for i := range eventsL1UserTx {
1173 eventsL1UserTx[i].L1UserTx.EthBlockNum = blockNum
1174 // Check validity of L1UserTx
1175 l1Tx, err := common.NewL1Tx(&eventsL1UserTx[i].L1UserTx)
1176 if err != nil {
1177 return nil, tracerr.Wrap(err)
1178 }
1179 l1Txs[i] = *l1Tx
1180 }
1181 return l1Txs, nil
1182}