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