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