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