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