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