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