package synchronizer import ( "context" "database/sql" "errors" "sync" ethCommon "github.com/ethereum/go-ethereum/common" "github.com/hermeznetwork/hermez-node/common" "github.com/hermeznetwork/hermez-node/db/historydb" "github.com/hermeznetwork/hermez-node/db/statedb" "github.com/hermeznetwork/hermez-node/eth" "github.com/hermeznetwork/hermez-node/log" ) const ( blocksToSync = 20 // TODO: This will be deleted once we can get the firstSavedBlock from the ethClient ) var ( // ErrNotAbleToSync is used when there is not possible to find a valid block to sync ErrNotAbleToSync = errors.New("it has not been possible to synchronize any block") ) // BatchData contains information about Batches from the contracts //nolint:structcheck,unused type BatchData struct { l1txs []common.L1Tx l2txs []common.L2Tx registeredAccounts []common.Account exitTree []common.ExitInfo } // BlockData contains information about Blocks from the contracts //nolint:structcheck,unused type BlockData struct { block *common.Block // Rollup batches []BatchData withdrawals []common.ExitInfo registeredTokens []common.Token rollupVars *common.RollupVars // Auction bids []common.Bid coordinators []common.Coordinator auctionVars *common.AuctionVars } // Status is returned by the Status method type Status struct { CurrentBlock int64 CurrentBatch common.BatchNum CurrentForgerAddr ethCommon.Address NextForgerAddr ethCommon.Address Synchronized bool } // Synchronizer implements the Synchronizer type type Synchronizer struct { ethClient *eth.Client historyDB *historydb.HistoryDB stateDB *statedb.StateDB firstSavedBlock *common.Block mux sync.Mutex } // NewSynchronizer creates a new Synchronizer func NewSynchronizer(ethClient *eth.Client, historyDB *historydb.HistoryDB, stateDB *statedb.StateDB) *Synchronizer { s := &Synchronizer{ ethClient: ethClient, historyDB: historyDB, stateDB: stateDB, } return s } // Sync updates History and State DB with information from the blockchain func (s *Synchronizer) Sync() error { // Avoid new sync while performing one s.mux.Lock() defer s.mux.Unlock() var lastStoredForgeL1TxsNum int64 // TODO: Get this information from ethClient once it's implemented // for the moment we will get the latestblock - 20 as firstSavedBlock latestBlock, err := s.ethClient.EthBlockByNumber(context.Background(), 0) if err != nil { return err } s.firstSavedBlock, err = s.ethClient.EthBlockByNumber(context.Background(), latestBlock.EthBlockNum-blocksToSync) if err != nil { return err } // Get lastSavedBlock from History DB lastSavedBlock, err := s.historyDB.GetLastBlock() if err != nil && err != sql.ErrNoRows { return err } // Check if we got a block or nil // In case of nil we must do a full sync if lastSavedBlock == nil || lastSavedBlock.EthBlockNum == 0 { lastSavedBlock = s.firstSavedBlock } else { // Get the latest block we have in History DB from blockchain to detect a reorg ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum) if err != nil { return err } if ethBlock.Hash != lastSavedBlock.Hash { // Reorg detected log.Debugf("Reorg Detected...") err := s.reorg(lastSavedBlock) if err != nil { return err } lastSavedBlock, err = s.historyDB.GetLastBlock() if err != nil { return err } } } log.Debugf("Syncing...") // Get latest blockNum in blockchain latestBlockNum, err := s.ethClient.EthCurrentBlock() if err != nil { return err } log.Debugf("Blocks to sync: %v (lastSavedBlock: %v, latestBlock: %v)", latestBlockNum-lastSavedBlock.EthBlockNum, lastSavedBlock.EthBlockNum, latestBlockNum) for lastSavedBlock.EthBlockNum < latestBlockNum { ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum+1) if err != nil { return err } // Get data from the rollup contract blockData, batchData, err := s.rollupSync(ethBlock, lastStoredForgeL1TxsNum) if err != nil { return err } // Get data from the auction contract err = s.auctionSync(blockData, batchData) if err != nil { return err } // Add rollupData and auctionData once the method is updated err = s.historyDB.AddBlock(ethBlock) if err != nil { return err } // We get the block on every iteration lastSavedBlock, err = s.historyDB.GetLastBlock() if err != nil { return err } } return nil } // reorg manages a reorg, updating History and State DB as needed func (s *Synchronizer) reorg(uncleBlock *common.Block) error { var block *common.Block blockNum := uncleBlock.EthBlockNum found := false log.Debugf("Reorg first uncle block: %v", blockNum) // Iterate History DB and the blokchain looking for the latest valid block for !found && blockNum > s.firstSavedBlock.EthBlockNum { ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum) if err != nil { return err } block, err = s.historyDB.GetBlock(blockNum) if err != nil { return err } if block.Hash == ethBlock.Hash { found = true log.Debugf("Found valid block: %v", blockNum) } else { log.Debugf("Discarding block: %v", blockNum) } blockNum-- } if found { // Set History DB and State DB to the correct state err := s.historyDB.Reorg(block.EthBlockNum) if err != nil { return err } batchNum, err := s.historyDB.GetLastBatchNum() if err != nil && err != sql.ErrNoRows { return err } if batchNum != 0 { err = s.stateDB.Reset(batchNum) if err != nil { return err } } return nil } return ErrNotAbleToSync } // Status returns current status values from the Synchronizer func (s *Synchronizer) Status() (*Status, error) { // Avoid possible inconsistencies s.mux.Lock() defer s.mux.Unlock() var status *Status // Get latest block in History DB lastSavedBlock, err := s.historyDB.GetLastBlock() if err != nil { return nil, err } status.CurrentBlock = lastSavedBlock.EthBlockNum // Get latest batch in History DB lastSavedBatch, err := s.historyDB.GetLastBatchNum() if err != nil && err != sql.ErrNoRows { return nil, err } status.CurrentBatch = lastSavedBatch // Get latest blockNum in blockchain latestBlockNum, err := s.ethClient.EthCurrentBlock() if err != nil { return nil, err } // TODO: Get CurrentForgerAddr & NextForgerAddr // Check if Synchronizer is synchronized status.Synchronized = status.CurrentBlock == latestBlockNum return status, nil } // rollupSync gets information from the Rollup Contract func (s *Synchronizer) rollupSync(block *common.Block, lastStoredForgeL1TxsNum int64) (*BlockData, []*BatchData, error) { // To be implemented return nil, nil, nil } // auctionSync gets information from the Auction Contract func (s *Synchronizer) auctionSync(blockData *BlockData, batchData []*BatchData) error { // To be implemented return nil }