hermez-node/synchronizer/synchronizer.go
synchronizer/synchronizer.goRaw
1package synchronizer
2
3import (
4 "context"
5 "database/sql"
6 "errors"
7 "strconv"
8 "sync"
9
10 "github.com/hermeznetwork/hermez-node/common"
11 "github.com/hermeznetwork/hermez-node/db/historydb"
12 "github.com/hermeznetwork/hermez-node/db/statedb"
13 "github.com/hermeznetwork/hermez-node/eth"
14 "github.com/hermeznetwork/hermez-node/log"
15)
16
17var (
18 // ErrNotAbleToSync is used when there is not possible to find a valid block to sync
19 ErrNotAbleToSync = errors.New("it has not been possible to synchronize any block")
20)
21
22// rollupData contains information returned by the Rollup SC
23type rollupData struct {
24 l1Txs []*common.L1Tx
25 batches []*BatchData
26 // withdrawals []*common.ExitInfo
27 registeredTokens []*common.Token
28 rollupVars *common.RollupVars
29}
30
31// NewRollupData creates an empty rollupData with the slices initialized.
32func newRollupData() rollupData {
33 return rollupData{
34 l1Txs: make([]*common.L1Tx, 0),
35 batches: make([]*BatchData, 0),
36 // withdrawals: make([]*common.ExitInfo, 0),
37 registeredTokens: make([]*common.Token, 0),
38 }
39}
40
41// auctionData contains information returned by the Action SC
42type auctionData struct {
43 bids []*common.Bid
44 coordinators []*common.Coordinator
45 auctionVars *common.AuctionVars
46}
47
48// newAuctionData creates an empty auctionData with the slices initialized.
49func newAuctionData() *auctionData {
50 return &auctionData{
51 bids: make([]*common.Bid, 0),
52 coordinators: make([]*common.Coordinator, 0),
53 }
54}
55
56// BatchData contains information about Batches from the contracts
57type BatchData struct {
58 l1UserTxs []*common.L1Tx
59 l1CoordinatorTxs []*common.L1Tx
60 l2Txs []*common.L2Tx
61 createdAccounts []*common.Account
62 exitTree []*common.ExitInfo
63 batch *common.Batch
64}
65
66// NewBatchData creates an empty BatchData with the slices initialized.
67func NewBatchData() *BatchData {
68 return &BatchData{
69 l1UserTxs: make([]*common.L1Tx, 0),
70 l1CoordinatorTxs: make([]*common.L1Tx, 0),
71 l2Txs: make([]*common.L2Tx, 0),
72 createdAccounts: make([]*common.Account, 0),
73 exitTree: make([]*common.ExitInfo, 0),
74 }
75}
76
77// BlockData contains information about Blocks from the contracts
78type BlockData struct {
79 block *common.Block
80 // Rollup
81 l1Txs []*common.L1Tx // TODO: Answer: User? Coordinator? Both?
82 batches []*BatchData // TODO: Also contains L1Txs!
83 // withdrawals []*common.ExitInfo // TODO
84 registeredTokens []*common.Token
85 rollupVars *common.RollupVars
86 // Auction
87 bids []*common.Bid
88 coordinators []*common.Coordinator
89 auctionVars *common.AuctionVars
90 // WithdrawalDelayer
91 withdrawalDelayerVars *common.WithdrawalDelayerVars
92}
93
94// Synchronizer implements the Synchronizer type
95type Synchronizer struct {
96 ethClient *eth.Client
97 historyDB *historydb.HistoryDB
98 stateDB *statedb.StateDB
99 firstSavedBlock *common.Block
100 mux sync.Mutex
101}
102
103// NewSynchronizer creates a new Synchronizer
104func NewSynchronizer(ethClient *eth.Client, historyDB *historydb.HistoryDB, stateDB *statedb.StateDB) *Synchronizer {
105 s := &Synchronizer{
106 ethClient: ethClient,
107 historyDB: historyDB,
108 stateDB: stateDB,
109 }
110 return s
111}
112
113// TODO: Be smart about locking: only lock during the read/write operations
114
115// Sync updates History and State DB with information from the blockchain
116// TODO: Return true if a new block was processed
117// TODO: Add argument: maximum number of blocks to process
118// TODO: Check reorgs in the middle of syncing a block. Probably make
119// rollupSync, auctionSync and withdrawalSync return the block hash.
120func (s *Synchronizer) Sync() error {
121 // Avoid new sync while performing one
122 s.mux.Lock()
123 defer s.mux.Unlock()
124
125 var nextBlockNum int64 // next block number to sync
126
127 // Get lastSavedBlock from History DB
128 lastSavedBlock, err := s.historyDB.GetLastBlock()
129 if err != nil && err != sql.ErrNoRows {
130 return err
131 }
132 // If we don't have any stored block, we must do a full sync starting from the rollup genesis block
133 if err == sql.ErrNoRows {
134 // TODO: Query rollup constants and genesis information, store them
135 nextBlockNum = 1234 // TODO: Replace this with genesisBlockNum
136 } else {
137 // Get the latest block we have in History DB from blockchain to detect a reorg
138 ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum)
139 if err != nil {
140 return err
141 }
142
143 if ethBlock.Hash != lastSavedBlock.Hash {
144 // Reorg detected
145 log.Debugf("Reorg Detected...")
146 _, err := s.reorg(lastSavedBlock)
147 if err != nil {
148 return err
149 }
150
151 lastSavedBlock, err = s.historyDB.GetLastBlock()
152 if err != nil {
153 return err
154 }
155 }
156 nextBlockNum = lastSavedBlock.EthBlockNum + 1
157 }
158
159 log.Debugf("Syncing...")
160
161 // Get latest blockNum in blockchain
162 latestBlockNum, err := s.ethClient.EthCurrentBlock()
163 if err != nil {
164 return err
165 }
166
167 log.Debugf("Blocks to sync: %v (firstBlockToSync: %v, latestBlock: %v)", latestBlockNum-nextBlockNum+1, nextBlockNum, latestBlockNum)
168
169 for nextBlockNum < latestBlockNum {
170 ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), nextBlockNum)
171 if err != nil {
172 return err
173 }
174 // TODO: Check that the obtianed ethBlock.ParentHash == prevEthBlock.Hash; if not, reorg!
175
176 // TODO: Send the ethHash in rollupSync(), auctionSync() and
177 // wdelayerSync() and make sure they all use the same block
178 // hash.
179
180 // Get data from the rollup contract
181 rollupData, err := s.rollupSync(nextBlockNum)
182 if err != nil {
183 return err
184 }
185
186 // Get data from the auction contract
187 auctionData, err := s.auctionSync(nextBlockNum)
188 if err != nil {
189 return err
190 }
191
192 // Get data from the WithdrawalDelayer contract
193 wdelayerData, err := s.wdelayerSync(nextBlockNum)
194 if err != nil {
195 return err
196 }
197
198 // Group all the block data into the structs to save into HistoryDB
199 var blockData BlockData
200
201 blockData.block = ethBlock
202
203 if rollupData != nil {
204 blockData.l1Txs = rollupData.l1Txs
205 blockData.batches = rollupData.batches
206 // blockData.withdrawals = rollupData.withdrawals // TODO
207 blockData.registeredTokens = rollupData.registeredTokens
208 blockData.rollupVars = rollupData.rollupVars
209 }
210
211 if auctionData != nil {
212 blockData.bids = auctionData.bids
213 blockData.coordinators = auctionData.coordinators
214 blockData.auctionVars = auctionData.auctionVars
215 }
216
217 if wdelayerData != nil {
218 blockData.withdrawalDelayerVars = wdelayerData
219 }
220
221 // Add rollupData and auctionData once the method is updated
222 // TODO: Save Whole Struct -> AddBlockSCData(blockData)
223 err = s.historyDB.AddBlock(blockData.block)
224 if err != nil {
225 return err
226 }
227 }
228
229 return nil
230}
231
232// reorg manages a reorg, updating History and State DB as needed. Keeps
233// checking previous blocks from the HistoryDB against the blockchain until a
234// block hash match is found. All future blocks in the HistoryDB and
235// corresponding batches in StateBD are discarded. Returns the last valid
236// blockNum from the HistoryDB.
237func (s *Synchronizer) reorg(uncleBlock *common.Block) (int64, error) {
238 var block *common.Block
239 blockNum := uncleBlock.EthBlockNum
240 found := false
241
242 log.Debugf("Reorg first uncle block: %v", blockNum)
243
244 // Iterate History DB and the blokchain looking for the latest valid block
245 for !found && blockNum > s.firstSavedBlock.EthBlockNum {
246 ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum)
247 if err != nil {
248 return 0, err
249 }
250
251 block, err = s.historyDB.GetBlock(blockNum)
252 if err != nil {
253 return 0, err
254 }
255 if block.Hash == ethBlock.Hash {
256 found = true
257 log.Debugf("Found valid block: %v", blockNum)
258 } else {
259 log.Debugf("Discarding block: %v", blockNum)
260 }
261
262 blockNum--
263 }
264
265 if found {
266 // Set History DB and State DB to the correct state
267 err := s.historyDB.Reorg(block.EthBlockNum)
268 if err != nil {
269 return 0, err
270 }
271
272 batchNum, err := s.historyDB.GetLastBatchNum()
273 if err != nil && err != sql.ErrNoRows {
274 return 0, err
275 }
276 if batchNum != 0 {
277 err = s.stateDB.Reset(batchNum)
278 if err != nil {
279 return 0, err
280 }
281 }
282
283 return block.EthBlockNum, nil
284 }
285
286 return 0, ErrNotAbleToSync
287}
288
289// Status returns current status values from the Synchronizer
290func (s *Synchronizer) Status() (*common.SyncStatus, error) {
291 // Avoid possible inconsistencies
292 s.mux.Lock()
293 defer s.mux.Unlock()
294
295 var status *common.SyncStatus
296
297 // TODO: Join all queries to the DB into a single transaction so that
298 // we can remove the mutex locking here:
299 // - HistoryDB.GetLastBlock
300 // - HistoryDB.GetLastBatchNum
301 // - HistoryDB.GetCurrentForgerAddr
302 // - HistoryDB.GetNextForgerAddr
303
304 // Get latest block in History DB
305 lastSavedBlock, err := s.historyDB.GetLastBlock()
306 if err != nil {
307 return nil, err
308 }
309 status.CurrentBlock = lastSavedBlock.EthBlockNum
310
311 // Get latest batch in History DB
312 lastSavedBatch, err := s.historyDB.GetLastBatchNum()
313 if err != nil && err != sql.ErrNoRows {
314 return nil, err
315 }
316 status.CurrentBatch = lastSavedBatch
317
318 // Get latest blockNum in blockchain
319 latestBlockNum, err := s.ethClient.EthCurrentBlock()
320 if err != nil {
321 return nil, err
322 }
323
324 // TODO: Get CurrentForgerAddr & NextForgerAddr from the Auction SC / Or from the HistoryDB
325
326 // Check if Synchronizer is synchronized
327 status.Synchronized = status.CurrentBlock == latestBlockNum
328 return status, nil
329}
330
331// rollupSync gets information from the Rollup Contract
332func (s *Synchronizer) rollupSync(blockNum int64) (*rollupData, error) {
333 var rollupData = newRollupData()
334 // var forgeL1TxsNum int64
335 var numAccounts int
336
337 // Get rollup events in the block
338 rollupEvents, _, err := s.ethClient.RollupEventsByBlock(blockNum)
339 if err != nil {
340 return nil, err
341 }
342
343 // TODO: Replace GetLastL1TxsNum by GetNextL1TxsNum
344 nextForgeL1TxsNum := int64(0)
345 nextForgeL1TxsNumPtr, err := s.historyDB.GetLastL1TxsNum()
346 if err != nil {
347 return nil, err
348 }
349 if nextForgeL1TxsNumPtr != nil {
350 nextForgeL1TxsNum = *nextForgeL1TxsNumPtr + 1
351 }
352
353 // Get newLastIdx that will be used to complete the accounts
354 // idx, err := s.getIdx(rollupEvents)
355 // if err != nil {
356 // return nil, err
357 // }
358
359 // Get L1UserTX
360 rollupData.l1Txs = getL1UserTx(rollupEvents.L1UserTx, blockNum)
361
362 // Get ForgeBatch events to get the L1CoordinatorTxs
363 for _, fbEvent := range rollupEvents.ForgeBatch {
364 batchData := NewBatchData()
365 position := 0
366
367 // Get the input for each Tx
368 forgeBatchArgs, err := s.ethClient.RollupForgeBatchArgs(fbEvent.EthTxHash)
369 if err != nil {
370 return nil, err
371 }
372 forgeL1TxsNum := int64(0)
373 // Check if this is a L1Batch to get L1 Tx from it
374 if forgeBatchArgs.L1Batch {
375 forgeL1TxsNum = nextForgeL1TxsNum
376
377 // Get L1 User Txs from History DB
378 // TODO: Get L1TX from HistoryDB filtered by toforgeL1txNum & fromidx = 0 and
379 // update batch number and add accounts to createdAccounts updating idx
380
381 // l1UserTxs, err := s.historyDB.GetL1UserTxs(nextForgeL1TxsNum)
382 // If HistoryDB doesn't have L1UserTxs at
383 // nextForgeL1TxsNum, check if they exist in
384 // rollupData.l1Txs. This could happen because in a
385 // block there could be multiple batches with L1Batch =
386 // true (although it's a very rare case). If the
387 // L1UserTxs are not in rollupData.l1Txs, use an empty
388 // array (this happens when the L1UserTxs queue is
389 // frozen but didn't store any tx).
390 l1UserTxs := []common.L1Tx{}
391 position = len(l1UserTxs)
392
393 // Get L1 Coordinator Txs
394 for _, l1CoordinatorTx := range forgeBatchArgs.L1CoordinatorTxs {
395 l1CoordinatorTx.Position = position
396 l1CoordinatorTx.ToForgeL1TxsNum = nextForgeL1TxsNum
397 l1CoordinatorTx.TxID = common.TxID(common.Hash([]byte("0x01" + strconv.FormatInt(int64(nextForgeL1TxsNum), 10) + strconv.FormatInt(int64(l1CoordinatorTx.Position), 10) + "00")))
398 l1CoordinatorTx.UserOrigin = false
399 l1CoordinatorTx.EthBlockNum = blockNum
400 bn := new(common.BatchNum)
401 *bn = common.BatchNum(fbEvent.BatchNum)
402 l1CoordinatorTx.BatchNum = bn
403
404 batchData.l1CoordinatorTxs = append(batchData.l1CoordinatorTxs, l1CoordinatorTx)
405
406 // Check if we have to register an account
407 // if l1CoordinatorTx.FromIdx == 0 {
408 // account := common.Account{
409 // // TODO: Uncommnent when common.account has IDx
410 // // IDx: common.Idx(idx),
411 // TokenID: l1CoordinatorTx.TokenID,
412 // Nonce: 0,
413 // Balance: l1CoordinatorTx.LoadAmount,
414 // PublicKey: l1CoordinatorTx.FromBJJ,
415 // EthAddr: l1CoordinatorTx.FromEthAddr,
416 // }
417 // idx++
418 // batchData.createdAccounts = append(batchData.createdAccounts, &account)
419 // numAccounts++
420 // }
421 position++
422 }
423 nextForgeL1TxsNum++
424 }
425
426 // Get L2Txs
427 poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2Txs) // TODO: This is a big uggly, find a better way
428
429 // Get exitTree
430 // TODO: Get createdAccounts from ProcessTxs()
431 // TODO: Get CollectedFees from ProcessTxs()
432 // TODO: Pass forgeBatchArgs.FeeIdxCoordinator to ProcessTxs()
433 _, exitInfo, err := s.stateDB.ProcessTxs(true, false, batchData.l1UserTxs, batchData.l1CoordinatorTxs, poolL2Txs)
434 if err != nil {
435 return nil, err
436 }
437
438 l2Txs := common.PoolL2TxsToL2Txs(poolL2Txs) // TODO: This is a big uggly, find a better way
439 batchData.l2Txs = append(batchData.l2Txs, l2Txs...)
440
441 batchData.exitTree = exitInfo
442
443 // Get Batch information
444 batch := &common.Batch{
445 BatchNum: common.BatchNum(fbEvent.BatchNum),
446 EthBlockNum: blockNum,
447 // ForgerAddr: , TODO: Get it from ethClient -> Add ForgerAddr to RollupEventForgeBatch
448 // CollectedFees: , TODO: Clarify where to get them if they are still needed
449 StateRoot: common.Hash(forgeBatchArgs.NewStRoot.Bytes()),
450 NumAccounts: numAccounts,
451 ExitRoot: common.Hash(forgeBatchArgs.NewExitRoot.Bytes()),
452 ForgeL1TxsNum: forgeL1TxsNum,
453 // SlotNum: TODO: Calculate once ethClient provides the info // calculate from blockNum + ethClient Constants
454 }
455 batchData.batch = batch
456 rollupData.batches = append(rollupData.batches, batchData)
457 }
458
459 // Get Registered Tokens
460 for _, eAddToken := range rollupEvents.AddToken {
461 var token *common.Token
462
463 token.TokenID = common.TokenID(eAddToken.TokenID)
464 token.EthAddr = eAddToken.Address
465 token.EthBlockNum = blockNum
466
467 // TODO: Add external information consulting SC about it using Address
468 rollupData.registeredTokens = append(rollupData.registeredTokens, token)
469 }
470
471 // TODO: rollupEvents.UpdateForgeL1L2BatchTimeout
472 // TODO: rollupEvents.UpdateFeeAddToken
473 // TODO: rollupEvents.WithdrawEvent
474
475 // TODO: Emergency Mechanism
476 // TODO: Variables
477 // TODO: Constants
478
479 return &rollupData, nil
480}
481
482// auctionSync gets information from the Auction Contract
483func (s *Synchronizer) auctionSync(blockNum int64) (*auctionData, error) {
484 var auctionData = newAuctionData()
485
486 // Get auction events in the block
487 auctionEvents, _, err := s.ethClient.AuctionEventsByBlock(blockNum)
488 if err != nil {
489 return nil, err
490 }
491
492 // Get bids
493 for _, eNewBid := range auctionEvents.NewBid {
494 bid := &common.Bid{
495 SlotNum: common.SlotNum(eNewBid.Slot),
496 BidValue: eNewBid.BidAmount,
497 ForgerAddr: eNewBid.CoordinatorForger,
498 EthBlockNum: blockNum,
499 }
500 auctionData.bids = append(auctionData.bids, bid)
501 }
502
503 // Get Coordinators
504 for _, eNewCoordinator := range auctionEvents.NewCoordinator {
505 coordinator := &common.Coordinator{
506 Forger: eNewCoordinator.ForgerAddress,
507 WithdrawAddr: eNewCoordinator.WithdrawalAddress,
508 URL: eNewCoordinator.CoordinatorURL,
509 }
510 auctionData.coordinators = append(auctionData.coordinators, coordinator)
511 }
512
513 // TODO: NewSlotDeadline
514 // TODO: NewClosedAuctionSlots
515 // TODO: NewOutbidding
516 // TODO: NewDonationAddress
517 // TODO: NewBootCoordinator
518 // TODO: NewOpenAuctionSlots
519 // TODO: NewAllocationRatio
520 // TODO: NewForgeAllocated
521 // TODO: NewDefaultSlotSetBid
522 // TODO: NewForge
523 // TODO: HEZClaimed
524
525 // TODO: Think about separating new coordinaors from coordinator updated
526
527 // Get Coordinators from updates
528 for _, eCoordinatorUpdated := range auctionEvents.CoordinatorUpdated {
529 coordinator := &common.Coordinator{
530 Forger: eCoordinatorUpdated.ForgerAddress,
531 WithdrawAddr: eCoordinatorUpdated.WithdrawalAddress,
532 URL: eCoordinatorUpdated.CoordinatorURL,
533 }
534 auctionData.coordinators = append(auctionData.coordinators, coordinator)
535 }
536
537 // TODO: VARS
538 // TODO: CONSTANTS
539
540 return auctionData, nil
541}
542
543// wdelayerSync gets information from the Withdrawal Delayer Contract
544func (s *Synchronizer) wdelayerSync(blockNum int64) (*common.WithdrawalDelayerVars, error) {
545 // TODO: VARS
546 // TODO: CONSTANTS
547
548 return nil, nil
549}
550
551// func (s *Synchronizer) getIdx(rollupEvents *eth.RollupEvents) (int64, error) {
552// // TODO: FIXME: There will be an error here when `len(rollupEvents.ForgeBatch) == 0`
553// lastForgeBatch := rollupEvents.ForgeBatch[len(rollupEvents.ForgeBatch)-1]
554//
555// // TODO: RollupForgeBatchArgs is already called in `rollupSync`.
556// // Ideally it should not need to be called twice for the same batch.
557// // Get the input for forgeBatch
558// forgeBatchArgs, err := s.ethClient.RollupForgeBatchArgs(lastForgeBatch.EthTxHash)
559// if err != nil {
560// return 0, err
561// }
562//
563// return forgeBatchArgs.NewLastIdx + 1, nil
564// }
565
566func getL1UserTx(l1UserTxEvents []eth.RollupEventL1UserTx, blockNum int64) []*common.L1Tx {
567 l1Txs := make([]*common.L1Tx, 0)
568
569 for _, eL1UserTx := range l1UserTxEvents {
570 // Fill aditional Tx fields
571 eL1UserTx.L1Tx.TxID = common.TxID(common.Hash([]byte("0x00" + strconv.FormatInt(int64(eL1UserTx.ToForgeL1TxsNum), 10) + strconv.FormatInt(int64(eL1UserTx.Position), 10) + "00")))
572 eL1UserTx.L1Tx.ToForgeL1TxsNum = eL1UserTx.ToForgeL1TxsNum
573 eL1UserTx.L1Tx.Position = eL1UserTx.Position
574 eL1UserTx.L1Tx.UserOrigin = true
575 eL1UserTx.L1Tx.EthBlockNum = blockNum
576
577 l1Txs = append(l1Txs, &eL1UserTx.L1Tx)
578 }
579 return l1Txs
580}