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	"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}