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