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