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