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