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}