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	ethCommon "github.com/ethereum/go-ethereum/common"
 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// BatchData contains information about Batches from the contracts
 27//nolint:structcheck,unused
 28type BatchData struct {
 29	l1txs              []common.L1Tx
 30	l2txs              []common.L2Tx
 31	registeredAccounts []common.Account
 32	exitTree           []common.ExitInfo
 33}
 34
 35// BlockData contains information about Blocks from the contracts
 36//nolint:structcheck,unused
 37type BlockData struct {
 38	block *common.Block
 39	// Rollup
 40	batches          []BatchData
 41	withdrawals      []common.ExitInfo
 42	registeredTokens []common.Token
 43	rollupVars       *common.RollupVars
 44	// Auction
 45	bids         []common.Bid
 46	coordinators []common.Coordinator
 47	auctionVars  *common.AuctionVars
 48}
 49
 50// Status is returned by the Status method
 51type Status struct {
 52	CurrentBlock      int64
 53	CurrentBatch      common.BatchNum
 54	CurrentForgerAddr ethCommon.Address
 55	NextForgerAddr    ethCommon.Address
 56	Synchronized      bool
 57}
 58
 59// Synchronizer implements the Synchronizer type
 60type Synchronizer struct {
 61	ethClient       *eth.Client
 62	historyDB       *historydb.HistoryDB
 63	stateDB         *statedb.StateDB
 64	firstSavedBlock *common.Block
 65	mux             sync.Mutex
 66}
 67
 68// NewSynchronizer creates a new Synchronizer
 69func NewSynchronizer(ethClient *eth.Client, historyDB *historydb.HistoryDB, stateDB *statedb.StateDB) *Synchronizer {
 70	s := &Synchronizer{
 71		ethClient: ethClient,
 72		historyDB: historyDB,
 73		stateDB:   stateDB,
 74	}
 75	return s
 76}
 77
 78// Sync updates History and State DB with information from the blockchain
 79func (s *Synchronizer) Sync() error {
 80	// Avoid new sync while performing one
 81	s.mux.Lock()
 82	defer s.mux.Unlock()
 83
 84	var lastStoredForgeL1TxsNum int64
 85
 86	// TODO: Get this information from ethClient once it's implemented
 87	// for the moment we will get the latestblock - 20 as firstSavedBlock
 88	latestBlock, err := s.ethClient.EthBlockByNumber(context.Background(), 0)
 89	if err != nil {
 90		return err
 91	}
 92	s.firstSavedBlock, err = s.ethClient.EthBlockByNumber(context.Background(), latestBlock.EthBlockNum-blocksToSync)
 93	if err != nil {
 94		return err
 95	}
 96
 97	// Get lastSavedBlock from History DB
 98	lastSavedBlock, err := s.historyDB.GetLastBlock()
 99	if err != nil && err != sql.ErrNoRows {
100		return err
101	}
102
103	// Check if we got a block or nil
104	// In case of nil we must do a full sync
105	if lastSavedBlock == nil || lastSavedBlock.EthBlockNum == 0 {
106		lastSavedBlock = s.firstSavedBlock
107	} else {
108		// Get the latest block we have in History DB from blockchain to detect a reorg
109		ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum)
110		if err != nil {
111			return err
112		}
113
114		if ethBlock.Hash != lastSavedBlock.Hash {
115			// Reorg detected
116			log.Debugf("Reorg Detected...")
117			err := s.reorg(lastSavedBlock)
118			if err != nil {
119				return err
120			}
121
122			lastSavedBlock, err = s.historyDB.GetLastBlock()
123			if err != nil {
124				return err
125			}
126		}
127	}
128
129	log.Debugf("Syncing...")
130
131	// Get latest blockNum in blockchain
132	latestBlockNum, err := s.ethClient.EthCurrentBlock()
133	if err != nil {
134		return err
135	}
136
137	log.Debugf("Blocks to sync: %v (lastSavedBlock: %v, latestBlock: %v)", latestBlockNum-lastSavedBlock.EthBlockNum, lastSavedBlock.EthBlockNum, latestBlockNum)
138
139	for lastSavedBlock.EthBlockNum < latestBlockNum {
140		ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), lastSavedBlock.EthBlockNum+1)
141		if err != nil {
142			return err
143		}
144
145		// Get data from the rollup contract
146		blockData, batchData, err := s.rollupSync(ethBlock, lastStoredForgeL1TxsNum)
147		if err != nil {
148			return err
149		}
150
151		// Get data from the auction contract
152		err = s.auctionSync(blockData, batchData)
153		if err != nil {
154			return err
155		}
156
157		// Add rollupData and auctionData once the method is updated
158		err = s.historyDB.AddBlock(ethBlock)
159		if err != nil {
160			return err
161		}
162
163		// We get the block on every iteration
164		lastSavedBlock, err = s.historyDB.GetLastBlock()
165		if err != nil {
166			return err
167		}
168	}
169
170	return nil
171}
172
173// reorg manages a reorg, updating History and State DB as needed
174func (s *Synchronizer) reorg(uncleBlock *common.Block) error {
175	var block *common.Block
176	blockNum := uncleBlock.EthBlockNum
177	found := false
178
179	log.Debugf("Reorg first uncle block: %v", blockNum)
180
181	// Iterate History DB and the blokchain looking for the latest valid block
182	for !found && blockNum > s.firstSavedBlock.EthBlockNum {
183		ethBlock, err := s.ethClient.EthBlockByNumber(context.Background(), blockNum)
184		if err != nil {
185			return err
186		}
187
188		block, err = s.historyDB.GetBlock(blockNum)
189		if err != nil {
190			return err
191		}
192		if block.Hash == ethBlock.Hash {
193			found = true
194			log.Debugf("Found valid block: %v", blockNum)
195		} else {
196			log.Debugf("Discarding block: %v", blockNum)
197		}
198
199		blockNum--
200	}
201
202	if found {
203		// Set History DB and State DB to the correct state
204		err := s.historyDB.Reorg(block.EthBlockNum)
205		if err != nil {
206			return err
207		}
208
209		batchNum, err := s.historyDB.GetLastBatchNum()
210		if err != nil && err != sql.ErrNoRows {
211			return err
212		}
213		if batchNum != 0 {
214			err = s.stateDB.Reset(batchNum)
215			if err != nil {
216				return err
217			}
218		}
219
220		return nil
221	}
222
223	return ErrNotAbleToSync
224}
225
226// Status returns current status values from the Synchronizer
227func (s *Synchronizer) Status() (*Status, error) {
228	// Avoid possible inconsistencies
229	s.mux.Lock()
230	defer s.mux.Unlock()
231
232	var status *Status
233
234	// Get latest block in History DB
235	lastSavedBlock, err := s.historyDB.GetLastBlock()
236	if err != nil {
237		return nil, err
238	}
239	status.CurrentBlock = lastSavedBlock.EthBlockNum
240
241	// Get latest batch in History DB
242	lastSavedBatch, err := s.historyDB.GetLastBatchNum()
243	if err != nil && err != sql.ErrNoRows {
244		return nil, err
245	}
246	status.CurrentBatch = lastSavedBatch
247
248	// Get latest blockNum in blockchain
249	latestBlockNum, err := s.ethClient.EthCurrentBlock()
250	if err != nil {
251		return nil, err
252	}
253
254	// TODO: Get CurrentForgerAddr & NextForgerAddr
255
256	// Check if Synchronizer is synchronized
257	status.Synchronized = status.CurrentBlock == latestBlockNum
258	return status, nil
259}
260
261// rollupSync gets information from the Rollup Contract
262func (s *Synchronizer) rollupSync(block *common.Block, lastStoredForgeL1TxsNum int64) (*BlockData, []*BatchData, error) {
263	// To be implemented
264	return nil, nil, nil
265}
266
267// auctionSync gets information from the Auction Contract
268func (s *Synchronizer) auctionSync(blockData *BlockData, batchData []*BatchData) error {
269	// To be implemented
270	return nil
271}