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, 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 _, l1CoordinatorTx := range forgeBatchArgs.L1CoordinatorTxs {
411 l1CoordinatorTx.Position = position
412 l1CoordinatorTx.ToForgeL1TxsNum = &forgeL1TxsNum
413 l1CoordinatorTx.UserOrigin = false
414 l1CoordinatorTx.EthBlockNum = blockNum
415 bn := new(common.BatchNum)
416 *bn = common.BatchNum(evtForgeBatch.BatchNum)
417 l1CoordinatorTx.BatchNum = bn
418 l1Tx, err := common.NewL1Tx(l1CoordinatorTx)
419 if err != nil {
420 return nil, err
421 }
422
423 batchData.L1CoordinatorTxs = append(batchData.L1CoordinatorTxs, *l1Tx)
424
425 // Check if we have to register an account
426 // if l1CoordinatorTx.FromIdx == 0 {
427 // account := common.Account{
428 // // TODO: Uncommnent when common.account has IDx
429 // // IDx: common.Idx(idx),
430 // TokenID: l1CoordinatorTx.TokenID,
431 // Nonce: 0,
432 // Balance: l1CoordinatorTx.LoadAmount,
433 // PublicKey: l1CoordinatorTx.FromBJJ,
434 // EthAddr: l1CoordinatorTx.FromEthAddr,
435 // }
436 // idx++
437 // batchData.createdAccounts = append(batchData.createdAccounts, &account)
438 // numAccounts++
439 // }
440 position++
441 }
442 nextForgeL1TxsNum++
443 }
444
445 // Get L2Txs
446 poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // TODO: This is a big uggly, find a better way
447
448 // Get exitTree
449 // TODO: Get createdAccounts from ProcessTxs()
450 // TODO: Get CollectedFees from ProcessTxs()
451 // TODO: Pass forgeBatchArgs.FeeIdxCoordinator to ProcessTxs()
452 _, exitInfo, err := s.stateDB.ProcessTxs(batchData.L1UserTxs, batchData.L1CoordinatorTxs, poolL2Txs)
453 if err != nil {
454 return nil, err
455 }
456
457 l2Txs, err := common.PoolL2TxsToL2Txs(poolL2Txs) // TODO: This is a big uggly, find a better way
458 if err != nil {
459 return nil, err
460 }
461 batchData.L2Txs = append(batchData.L2Txs, l2Txs...)
462
463 batchData.ExitTree = exitInfo
464
465 // Get Batch information
466 batch := &common.Batch{
467 BatchNum: common.BatchNum(evtForgeBatch.BatchNum),
468 EthBlockNum: blockNum,
469 // ForgerAddr: , TODO: Get it from ethClient -> Add ForgerAddr to RollupEventForgeBatch
470 // CollectedFees: , TODO: Clarify where to get them if they are still needed
471 StateRoot: common.Hash(forgeBatchArgs.NewStRoot.Bytes()),
472 NumAccounts: numAccounts,
473 ExitRoot: common.Hash(forgeBatchArgs.NewExitRoot.Bytes()),
474 ForgeL1TxsNum: &forgeL1TxsNum,
475 // SlotNum: TODO: Calculate once ethClient provides the info // calculate from blockNum + ethClient Constants
476 }
477 batchData.Batch = batch
478 rollupData.batches = append(rollupData.batches, *batchData)
479 }
480
481 // Get Registered Tokens
482 for _, evtAddToken := range rollupEvents.AddToken {
483 var token common.Token
484
485 token.TokenID = common.TokenID(evtAddToken.TokenID)
486 token.EthAddr = evtAddToken.Address
487 token.EthBlockNum = blockNum
488
489 // TODO: Add external information consulting SC about it using Address
490 token.Name = "TODO"
491 token.Symbol = "TODO"
492 token.Decimals = 8 // TODO
493
494 rollupData.registeredTokens = append(rollupData.registeredTokens, token)
495 }
496
497 // TODO: rollupEvents.UpdateForgeL1L2BatchTimeout
498 // TODO: rollupEvents.UpdateFeeAddToken
499 // TODO: rollupEvents.WithdrawEvent
500
501 // TODO: Emergency Mechanism
502 // TODO: Variables
503 // TODO: Constants
504
505 return &rollupData, nil
506}
507
508// auctionSync gets information from the Auction Contract
509func (s *Synchronizer) auctionSync(blockNum int64) (*auctionData, error) {
510 var auctionData = newAuctionData()
511
512 // Get auction events in the block
513 auctionEvents, _, err := s.ethClient.AuctionEventsByBlock(blockNum)
514 if err != nil {
515 return nil, err
516 }
517
518 // Get bids
519 for _, evtNewBid := range auctionEvents.NewBid {
520 bid := common.Bid{
521 SlotNum: common.SlotNum(evtNewBid.Slot),
522 BidValue: evtNewBid.BidAmount,
523 Bidder: evtNewBid.Bidder,
524 EthBlockNum: blockNum,
525 }
526 auctionData.bids = append(auctionData.bids, bid)
527 }
528
529 // Get Coordinators
530 for _, evtSetCoordinator := range auctionEvents.SetCoordinator {
531 coordinator := common.Coordinator{
532 Bidder: evtSetCoordinator.BidderAddress,
533 Forger: evtSetCoordinator.ForgerAddress,
534 URL: evtSetCoordinator.CoordinatorURL,
535 }
536 auctionData.coordinators = append(auctionData.coordinators, coordinator)
537 }
538
539 // TODO: NewSlotDeadline
540 // TODO: NewClosedAuctionSlots
541 // TODO: NewOutbidding
542 // TODO: NewDonationAddress
543 // TODO: NewBootCoordinator
544 // TODO: NewOpenAuctionSlots
545 // TODO: NewAllocationRatio
546 // TODO: NewForgeAllocated
547 // TODO: NewDefaultSlotSetBid
548 // TODO: NewForge
549 // TODO: HEZClaimed
550
551 // TODO: VARS
552 // TODO: CONSTANTS
553
554 return auctionData, nil
555}
556
557// wdelayerSync gets information from the Withdrawal Delayer Contract
558func (s *Synchronizer) wdelayerSync(blockNum int64) (*wdelayerData, error) {
559 // TODO: VARS
560 // TODO: CONSTANTS
561
562 return nil, nil
563}
564
565// func (s *Synchronizer) getIdx(rollupEvents *eth.RollupEvents) (int64, error) {
566// // TODO: FIXME: There will be an error here when `len(rollupEvents.ForgeBatch) == 0`
567// lastForgeBatch := rollupEvents.ForgeBatch[len(rollupEvents.ForgeBatch)-1]
568//
569// // TODO: RollupForgeBatchArgs is already called in `rollupSync`.
570// // Ideally it should not need to be called twice for the same batch.
571// // Get the input for forgeBatch
572// forgeBatchArgs, err := s.ethClient.RollupForgeBatchArgs(lastForgeBatch.EthTxHash)
573// if err != nil {
574// return 0, err
575// }
576//
577// return forgeBatchArgs.NewLastIdx + 1, nil
578// }
579
580func getL1UserTx(eventsL1UserTx []eth.RollupEventL1UserTx, blockNum int64) ([]common.L1Tx, error) {
581 l1Txs := make([]common.L1Tx, 0)
582
583 for _, evtL1UserTx := range eventsL1UserTx {
584 // Fill aditional Tx fields
585 toForge := evtL1UserTx.ToForgeL1TxsNum
586 evtL1UserTx.L1Tx.ToForgeL1TxsNum = &toForge
587 evtL1UserTx.L1Tx.Position = evtL1UserTx.Position
588 evtL1UserTx.L1Tx.UserOrigin = true
589 evtL1UserTx.L1Tx.EthBlockNum = blockNum
590 nL1Tx, err := common.NewL1Tx(&evtL1UserTx.L1Tx)
591 if err != nil {
592 return nil, err
593 }
594 evtL1UserTx.L1Tx = *nL1Tx
595
596 l1Txs = append(l1Txs, evtL1UserTx.L1Tx)
597 }
598 return l1Txs, nil
599}