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}