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