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
 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	lastStoredForgeL1TxsNum, err := s.historyDB.GetLastL1TxsNum()
335
336	if err != nil {
337		return nil, err
338	}
339
340	// Get rollup events in the block
341	rollupEvents, _, err := s.ethClient.RollupEventsByBlock(block.EthBlockNum)
342
343	if err != nil {
344		return nil, err
345	}
346
347	// Get newLastIdx that will be used to complete the accounts
348	idx, err := s.getIdx(rollupEvents)
349
350	if err != nil {
351		return nil, err
352	}
353
354	// Get L1UserTX
355	rollupData.l1Txs = s.getL1UserTx(rollupEvents.L1UserTx, block)
356
357	// Get ForgeBatch events to get the L1CoordinatorTxs
358	for _, fbEvent := range rollupEvents.ForgeBatch {
359		batchData := NewBatchData()
360
361		// TODO: Get position from HistoryDB filtering by
362		// to_forge_l1_txs_num and batch_num and latest position, then add 1
363		position := 1
364
365		// Get the input for each Tx
366		forgeBatchArgs, err := s.ethClient.RollupForgeBatchArgs(fbEvent.EthTxHash)
367
368		if err != nil {
369			return nil, err
370		}
371
372		// Check if this is a L1Bath to get L1 Tx from it
373		if forgeBatchArgs.L1Batch {
374			// Get L1 User Txs from History DB
375			// TODO: Get L1TX from HistoryDB filtered by toforgeL1txNum & fromidx = 0 and
376			// update batch number and add accounts to registeredAccounts updating idx
377
378			// l1UserTxs, err := s.historyDB.GetL1UserTxs(lastStoredForgeL1TxsNum)
379
380			// Get L1 Coordinator Txs
381			for _, l1CoordinatorTx := range forgeBatchArgs.L1CoordinatorTxs {
382				l1CoordinatorTx.Position = position
383				l1CoordinatorTx.ToForgeL1TxsNum = uint32(lastStoredForgeL1TxsNum)
384				l1CoordinatorTx.TxID = common.TxID(common.Hash([]byte("0x01" + strconv.FormatInt(int64(lastStoredForgeL1TxsNum), 10) + strconv.FormatInt(int64(l1CoordinatorTx.Position), 10) + "00")))
385				l1CoordinatorTx.UserOrigin = false
386				l1CoordinatorTx.EthBlockNum = uint64(block.EthBlockNum)
387				l1CoordinatorTx.BatchNum = common.BatchNum(fbEvent.BatchNum)
388
389				batchData.l1CoordinatorTxs = append(batchData.l1CoordinatorTxs, l1CoordinatorTx)
390
391				forgeL1TxsNum++
392
393				// Check if we have to register an account
394				if l1CoordinatorTx.FromIdx == 0 {
395					account := common.Account{
396						// TODO: Uncommnent when common.account has IDx
397						// IDx:       common.Idx(idx),
398						TokenID:   l1CoordinatorTx.TokenID,
399						Nonce:     0,
400						Balance:   l1CoordinatorTx.LoadAmount,
401						PublicKey: l1CoordinatorTx.FromBJJ,
402						EthAddr:   l1CoordinatorTx.FromEthAddr,
403					}
404
405					idx++
406
407					batchData.registeredAccounts = append(batchData.registeredAccounts, &account)
408
409					numAccounts++
410				}
411
412				position++
413			}
414
415			lastStoredForgeL1TxsNum++
416		}
417
418		// Get L2Txs
419		batchData.l2Txs = append(batchData.l2Txs, forgeBatchArgs.L2Txs...)
420
421		// Get exitTree
422		_, exitInfo, err := s.stateDB.ProcessTxs(true, batchData.l1UserTxs, batchData.l1CoordinatorTxs, batchData.l2Txs)
423
424		if err != nil {
425			return nil, err
426		}
427
428		batchData.exitTree = exitInfo
429
430		// Get Batch information
431		batch := &common.Batch{
432			BatchNum:    common.BatchNum(fbEvent.BatchNum),
433			EthBlockNum: block.EthBlockNum,
434			// ForgerAddr: , TODO: Get it from ethClient
435			// CollectedFees: , TODO: Clarify where to get them if they are still needed
436			StateRoot:     common.Hash(forgeBatchArgs.NewStRoot.Bytes()),
437			NumAccounts:   numAccounts,
438			ExitRoot:      common.Hash(forgeBatchArgs.NewExitRoot.Bytes()),
439			ForgeL1TxsNum: forgeL1TxsNum,
440			// SlotNum: TODO: Calculate once ethClient provides the info
441		}
442
443		batchData.batch = batch
444
445		rollupData.batches = append(rollupData.batches, batchData)
446	}
447
448	// Get Registered Tokens
449	for _, eAddToken := range rollupEvents.AddToken {
450		var token *common.Token
451
452		token.TokenID = common.TokenID(eAddToken.TokenID)
453		token.EthAddr = eAddToken.Address
454		token.EthBlockNum = uint64(block.EthBlockNum)
455
456		// TODO: Add external information consulting SC about it using Address
457		rollupData.registeredTokens = append(rollupData.registeredTokens, token)
458	}
459
460	// TODO: Emergency Mechanism
461	// TODO: Variables
462	// TODO: Constants
463
464	return &rollupData, nil
465}
466
467// auctionSync gets information from the Auction Contract
468func (s *Synchronizer) auctionSync(block *common.Block) (*auctionData, error) {
469	var auctionData = newAuctionData()
470
471	// Get auction events in the block
472	auctionEvents, _, err := s.ethClient.AuctionEventsByBlock(block.EthBlockNum)
473
474	if err != nil {
475		return nil, err
476	}
477
478	// Get bids
479	for _, eNewBid := range auctionEvents.NewBid {
480		bid := &common.Bid{
481
482			SlotNum:     common.SlotNum(eNewBid.Slot),
483			BidValue:    eNewBid.BidAmount,
484			ForgerAddr:  eNewBid.CoordinatorForger,
485			EthBlockNum: block.EthBlockNum,
486		}
487		auctionData.bids = append(auctionData.bids, bid)
488	}
489
490	// Get Coordinators
491	for _, eNewCoordinator := range auctionEvents.NewCoordinator {
492		coordinator := &common.Coordinator{
493			Forger:   eNewCoordinator.ForgerAddress,
494			Withdraw: eNewCoordinator.WithdrawalAddress,
495			URL:      eNewCoordinator.URL,
496		}
497		auctionData.coordinators = append(auctionData.coordinators, coordinator)
498	}
499
500	// Get Coordinators from updates
501	for _, eCoordinatorUpdated := range auctionEvents.CoordinatorUpdated {
502		coordinator := &common.Coordinator{
503			Forger:   eCoordinatorUpdated.ForgerAddress,
504			Withdraw: eCoordinatorUpdated.WithdrawalAddress,
505			URL:      eCoordinatorUpdated.URL,
506		}
507		auctionData.coordinators = append(auctionData.coordinators, coordinator)
508	}
509
510	// TODO: VARS
511	// TODO: CONSTANTS
512
513	return auctionData, nil
514}
515
516// wdelayerSync gets information from the Withdrawal Delayer Contract
517func (s *Synchronizer) wdelayerSync(block *common.Block) (*common.WithdrawalDelayerVars, error) {
518	// TODO: VARS
519	// TODO: CONSTANTS
520
521	return nil, nil
522}
523
524func (s *Synchronizer) getIdx(rollupEvents *eth.RollupEvents) (int64, error) {
525	lastForgeBatch := rollupEvents.ForgeBatch[len(rollupEvents.ForgeBatch)-1]
526
527	// Get the input for forgeBatch
528	forgeBatchArgs, err := s.ethClient.RollupForgeBatchArgs(lastForgeBatch.EthTxHash)
529
530	if err != nil {
531		return 0, err
532	}
533
534	return forgeBatchArgs.NewLastIdx + 1, nil
535}
536
537func (s *Synchronizer) getL1UserTx(l1UserTxEvents []eth.RollupEventL1UserTx, block *common.Block) []*common.L1Tx {
538	l1Txs := make([]*common.L1Tx, 0)
539
540	for _, eL1UserTx := range l1UserTxEvents {
541		// Fill aditional Tx fields
542		eL1UserTx.L1Tx.TxID = common.TxID(common.Hash([]byte("0x00" + strconv.FormatInt(int64(eL1UserTx.ToForgeL1TxsNum), 10) + strconv.FormatInt(int64(eL1UserTx.Position), 10) + "00")))
543		eL1UserTx.L1Tx.ToForgeL1TxsNum = uint32(eL1UserTx.ToForgeL1TxsNum)
544		eL1UserTx.L1Tx.Position = eL1UserTx.Position
545		eL1UserTx.L1Tx.UserOrigin = true
546		eL1UserTx.L1Tx.EthBlockNum = uint64(block.EthBlockNum)
547		eL1UserTx.L1Tx.BatchNum = 0
548
549		l1Txs = append(l1Txs, &eL1UserTx.L1Tx)
550	}
551	return l1Txs
552}