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