hermez-node/synchronizer/synchronizer_test.go
synchronizer/synchronizer_test.goRaw
1package synchronizer
2
3import (
4 "context"
5 "encoding/json"
6 "fmt"
7 "io/ioutil"
8 "math/big"
9 "os"
10 "testing"
11
12 ethCommon "github.com/ethereum/go-ethereum/common"
13 "github.com/hermeznetwork/hermez-node/common"
14 dbUtils "github.com/hermeznetwork/hermez-node/db"
15 "github.com/hermeznetwork/hermez-node/db/historydb"
16 "github.com/hermeznetwork/hermez-node/db/statedb"
17 "github.com/hermeznetwork/hermez-node/eth"
18 "github.com/hermeznetwork/hermez-node/test"
19 "github.com/hermeznetwork/hermez-node/test/til"
20 "github.com/jinzhu/copier"
21 "github.com/stretchr/testify/assert"
22 "github.com/stretchr/testify/require"
23)
24
25var tokenConsts = map[common.TokenID]eth.ERC20Consts{}
26var forceExits = map[int64][]common.ExitInfo{} // ForgeL1TxsNum -> []exit
27
28type timer struct {
29 time int64
30}
31
32func (t *timer) Time() int64 {
33 currentTime := t.time
34 t.time++
35 return currentTime
36}
37
38// Check Sync output and HistoryDB state against expected values generated by
39// til
40func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBlock *common.BlockData) {
41 // Check Blocks
42 dbBlocks, err := s.historyDB.GetAllBlocks()
43 require.Nil(t, err)
44 assert.Equal(t, blockNum, len(dbBlocks))
45 assert.Equal(t, int64(blockNum), dbBlocks[blockNum-1].EthBlockNum)
46 assert.NotEqual(t, dbBlocks[blockNum-1].Hash, dbBlocks[blockNum-2].Hash)
47 assert.Greater(t, dbBlocks[blockNum-1].Timestamp.Unix(), dbBlocks[blockNum-2].Timestamp.Unix())
48
49 // Check Tokens
50 assert.Equal(t, len(block.AddedTokens), len(syncBlock.AddedTokens))
51 dbTokens, err := s.historyDB.GetAllTokens()
52 require.Nil(t, err)
53 for i, token := range block.AddedTokens {
54 dbToken := dbTokens[i]
55 syncToken := syncBlock.AddedTokens[i]
56
57 assert.Equal(t, block.Block.EthBlockNum, syncToken.EthBlockNum)
58 assert.Equal(t, token.TokenID, syncToken.TokenID)
59 assert.Equal(t, token.EthAddr, syncToken.EthAddr)
60 tokenConst := tokenConsts[token.TokenID]
61 assert.Equal(t, tokenConst.Name, syncToken.Name)
62 assert.Equal(t, tokenConst.Symbol, syncToken.Symbol)
63 assert.Equal(t, tokenConst.Decimals, syncToken.Decimals)
64
65 var tokenCpy historydb.TokenWithUSD
66 //nolint:gosec
67 require.Nil(t, copier.Copy(&tokenCpy, &token)) // copy common.Token to historydb.TokenWithUSD
68 require.Nil(t, copier.Copy(&tokenCpy, &tokenConst)) // copy common.Token to historydb.TokenWithUSD
69 tokenCpy.ItemID = dbToken.ItemID // we don't care about ItemID
70 assert.Equal(t, tokenCpy, dbToken)
71 }
72
73 // Check L1UserTxs
74 assert.Equal(t, len(block.L1UserTxs), len(syncBlock.L1UserTxs))
75 dbL1UserTxs, err := s.historyDB.GetAllL1UserTxs()
76 require.Nil(t, err)
77 // Ignore BatchNum in syncBlock.L1UserTxs because this value is set by the HistoryDB
78 for i := range syncBlock.L1UserTxs {
79 syncBlock.L1UserTxs[i].BatchNum = block.L1UserTxs[i].BatchNum
80 }
81 assert.Equal(t, block.L1UserTxs, syncBlock.L1UserTxs)
82 for _, tx := range block.L1UserTxs {
83 var dbTx *common.L1Tx
84 // Find tx in DB output
85 for _, _dbTx := range dbL1UserTxs {
86 if *tx.ToForgeL1TxsNum == *_dbTx.ToForgeL1TxsNum &&
87 tx.Position == _dbTx.Position {
88 dbTx = new(common.L1Tx)
89 *dbTx = _dbTx
90 break
91 }
92 }
93 assert.Equal(t, &tx, dbTx) //nolint:gosec
94 }
95
96 // Check Batches
97 assert.Equal(t, len(block.Batches), len(syncBlock.Batches))
98 dbBatches, err := s.historyDB.GetAllBatches()
99 require.Nil(t, err)
100
101 dbL1CoordinatorTxs, err := s.historyDB.GetAllL1CoordinatorTxs()
102 require.Nil(t, err)
103 // fmt.Printf("DBG dbL1CoordinatorTxs: %+v\n", dbL1CoordinatorTxs)
104 dbL2Txs, err := s.historyDB.GetAllL2Txs()
105 require.Nil(t, err)
106 // fmt.Printf("DBG dbL2Txs: %+v\n", dbL2Txs)
107 dbExits, err := s.historyDB.GetAllExits()
108 require.Nil(t, err)
109 // dbL1CoordinatorTxs := []common.L1Tx{}
110 for i, batch := range block.Batches {
111 var dbBatch *common.Batch
112 // Find batch in DB output
113 for _, _dbBatch := range dbBatches {
114 if batch.Batch.BatchNum == _dbBatch.BatchNum {
115 dbBatch = new(common.Batch)
116 *dbBatch = _dbBatch
117 break
118 }
119 }
120 syncBatch := syncBlock.Batches[i]
121
122 // We don't care about TotalFeesUSD. Use the syncBatch that
123 // has a TotalFeesUSD inserted by the HistoryDB
124 batch.Batch.TotalFeesUSD = syncBatch.Batch.TotalFeesUSD
125 batch.CreatedAccounts = syncBatch.CreatedAccounts // til doesn't output CreatedAccounts
126
127 // Test field by field to facilitate debugging of errors
128 assert.Equal(t, batch.L1CoordinatorTxs, syncBatch.L1CoordinatorTxs)
129 assert.Equal(t, batch.L2Txs, syncBatch.L2Txs)
130 // In exit tree, we only check AccountIdx and Balance, because
131 // it's what we have precomputed before.
132 for j := range batch.ExitTree {
133 exit := &batch.ExitTree[j]
134 assert.Equal(t, exit.AccountIdx, syncBatch.ExitTree[j].AccountIdx)
135 assert.Equal(t, exit.Balance, syncBatch.ExitTree[j].Balance)
136 *exit = syncBatch.ExitTree[j]
137 }
138 assert.Equal(t, batch.Batch, syncBatch.Batch)
139 assert.Equal(t, batch, syncBatch)
140 assert.Equal(t, &batch.Batch, dbBatch) //nolint:gosec
141
142 // Check L1CoordinatorTxs from DB
143 for _, tx := range batch.L1CoordinatorTxs {
144 var dbTx *common.L1Tx
145 // Find tx in DB output
146 for _, _dbTx := range dbL1CoordinatorTxs {
147 if *tx.BatchNum == *_dbTx.BatchNum &&
148 tx.Position == _dbTx.Position {
149 dbTx = new(common.L1Tx)
150 *dbTx = _dbTx
151 break
152 }
153 }
154 assert.Equal(t, &tx, dbTx) //nolint:gosec
155 }
156
157 // Check L2Txs from DB
158 for _, tx := range batch.L2Txs {
159 var dbTx *common.L2Tx
160 // Find tx in DB output
161 for _, _dbTx := range dbL2Txs {
162 if tx.BatchNum == _dbTx.BatchNum &&
163 tx.Position == _dbTx.Position {
164 dbTx = new(common.L2Tx)
165 *dbTx = _dbTx
166 break
167 }
168 }
169 assert.Equal(t, &tx, dbTx) //nolint:gosec
170 }
171
172 // Check Exits from DB
173 for _, exit := range batch.ExitTree {
174 var dbExit *common.ExitInfo
175 // Find exit in DB output
176 for _, _dbExit := range dbExits {
177 if exit.BatchNum == _dbExit.BatchNum &&
178 exit.AccountIdx == _dbExit.AccountIdx {
179 dbExit = new(common.ExitInfo)
180 *dbExit = _dbExit
181 break
182 }
183 }
184 // Compare MerkleProof in JSON because unmarshaled 0
185 // big.Int leaves the internal big.Int array at nil,
186 // and gives trouble when comparing big.Int with
187 // internal big.Int array != nil but empty.
188 mtp, err := json.Marshal(exit.MerkleProof)
189 require.Nil(t, err)
190 dbMtp, err := json.Marshal(dbExit.MerkleProof)
191 require.Nil(t, err)
192 assert.Equal(t, mtp, dbMtp)
193 dbExit.MerkleProof = exit.MerkleProof
194 assert.Equal(t, &exit, dbExit) //nolint:gosec
195 }
196 }
197}
198
199func TestSync(t *testing.T) {
200 //
201 // Setup
202 //
203
204 ctx := context.Background()
205 // Int State DB
206 dir, err := ioutil.TempDir("", "tmpdb")
207 require.Nil(t, err)
208 defer assert.Nil(t, os.RemoveAll(dir))
209
210 stateDB, err := statedb.NewStateDB(dir, statedb.TypeSynchronizer, 32)
211 assert.Nil(t, err)
212
213 // Init History DB
214 pass := os.Getenv("POSTGRES_PASS")
215 db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
216 require.Nil(t, err)
217 historyDB := historydb.NewHistoryDB(db)
218 // Clear DB
219 err = historyDB.Reorg(-1)
220 assert.Nil(t, err)
221
222 // Init eth client
223 var timer timer
224 clientSetup := test.NewClientSetupExample()
225 bootCoordAddr := clientSetup.AuctionVariables.BootCoordinator
226 client := test.NewClient(true, &timer, ðCommon.Address{}, clientSetup)
227
228 // Create Synchronizer
229 s, err := NewSynchronizer(client, historyDB, stateDB)
230 require.Nil(t, err)
231
232 //
233 // First Sync from an initial state
234 //
235
236 // Test Sync for rollup genesis block
237 syncBlock, discards, err := s.Sync2(ctx, nil)
238 require.Nil(t, err)
239 require.Nil(t, discards)
240 require.NotNil(t, syncBlock)
241 assert.Equal(t, int64(1), syncBlock.Block.EthBlockNum)
242 dbBlocks, err := s.historyDB.GetAllBlocks()
243 require.Nil(t, err)
244 assert.Equal(t, 1, len(dbBlocks))
245 assert.Equal(t, int64(1), dbBlocks[0].EthBlockNum)
246
247 // Sync again and expect no new blocks
248 syncBlock, discards, err = s.Sync2(ctx, nil)
249 require.Nil(t, err)
250 require.Nil(t, discards)
251 require.Nil(t, syncBlock)
252
253 //
254 // Generate blockchain and smart contract data, and fill the test smart contracts
255 //
256
257 // TODO: Test CreateAccountDepositTransfer
258 // TODO: Test ForceTransfer
259 // TODO: Test ForceExit
260
261 // Generate blockchain data with til
262 set1 := `
263 Type: Blockchain
264
265 AddToken(1)
266 AddToken(2)
267 AddToken(3)
268
269 CreateAccountDeposit(1) C: 20 // Idx=256+2=258
270 CreateAccountDeposit(2) A: 20 // Idx=256+3=259
271 CreateAccountDeposit(1) D: 5 // Idx=256+4=260
272 CreateAccountDeposit(2) B: 5 // Idx=256+5=261
273 CreateAccountDeposit(2) C: 5 // Idx=256+6=262
274
275 CreateAccountCoordinator(1) A // Idx=256+0=256
276 CreateAccountCoordinator(1) B // Idx=256+1=257
277
278 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs
279 > batchL1 // forge defined L1UserTxs, freeze L1UserTxs{nil}
280 > block // blockNum=2
281
282 Transfer(1) C-A: 10 (10)
283 Exit(1) D: 3 (5)
284
285 > batch
286 > block // blockNum=3
287
288 `
289 tc := til.NewContext(eth.RollupConstMaxL1UserTx)
290 blocks, err := tc.GenerateBlocks(set1)
291 require.Nil(t, err)
292 // Sanity check
293 require.Equal(t, 2, len(blocks))
294 // blocks 0 (blockNum=2)
295 i := 0
296 require.Equal(t, 2, int(blocks[i].Block.EthBlockNum))
297 require.Equal(t, 3, len(blocks[i].AddedTokens))
298 require.Equal(t, 5, len(blocks[i].L1UserTxs))
299 require.Equal(t, 2, len(blocks[i].Batches))
300 require.Equal(t, 2, len(blocks[i].Batches[0].L1CoordinatorTxs))
301 // blocks 1 (blockNum=3)
302 i = 1
303 require.Equal(t, 3, int(blocks[i].Block.EthBlockNum))
304 require.Equal(t, 1, len(blocks[i].Batches))
305 require.Equal(t, 2, len(blocks[i].Batches[0].L2Txs))
306
307 // Generate extra required data
308 for _, block := range blocks {
309 for _, token := range block.AddedTokens {
310 consts := eth.ERC20Consts{
311 Name: fmt.Sprintf("Token %d", token.TokenID),
312 Symbol: fmt.Sprintf("TK%d", token.TokenID),
313 Decimals: 18,
314 }
315 tokenConsts[token.TokenID] = consts
316 client.CtlAddERC20(token.EthAddr, consts)
317 }
318 }
319
320 // Add block data to the smart contracts
321 for _, block := range blocks {
322 for _, token := range block.AddedTokens {
323 _, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
324 require.Nil(t, err)
325 }
326 for _, tx := range block.L1UserTxs {
327 client.CtlSetAddr(tx.FromEthAddr)
328 _, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
329 uint32(tx.TokenID), int64(tx.ToIdx))
330 require.Nil(t, err)
331 }
332 client.CtlSetAddr(bootCoordAddr)
333 for _, batch := range block.Batches {
334 _, err := client.RollupForgeBatch(ð.RollupForgeBatchArgs{
335 NewLastIdx: batch.Batch.LastIdx,
336 NewStRoot: batch.Batch.StateRoot,
337 NewExitRoot: batch.Batch.ExitRoot,
338 L1CoordinatorTxs: batch.L1CoordinatorTxs,
339 L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
340 L2TxsData: batch.L2Txs,
341 FeeIdxCoordinator: []common.Idx{}, // TODO
342 // Circuit selector
343 VerifierIdx: 0, // Intentionally empty
344 L1Batch: batch.L1Batch,
345 ProofA: [2]*big.Int{}, // Intentionally empty
346 ProofB: [2][2]*big.Int{}, // Intentionally empty
347 ProofC: [2]*big.Int{}, // Intentionally empty
348 })
349 require.Nil(t, err)
350 }
351 // Mine block and sync
352 client.CtlMineBlock()
353 }
354
355 // Fill extra fields not generated by til in til block
356 openToForge := int64(0)
357 toForgeL1TxsNum := int64(0)
358 l1UserTxsLen := map[int64]int{} // ForgeL1TxsNum -> len(L1UserTxs)
359 for i := range blocks {
360 block := &blocks[i]
361 // Count number of L1UserTxs in each queue, to figure out later
362 // position of L1CoordinatorTxs and L2Txs
363 for j := range block.L1UserTxs {
364 tx := &block.L1UserTxs[j]
365 l1UserTxsLen[*tx.ToForgeL1TxsNum]++
366 if tx.Type == common.TxTypeForceExit {
367 forceExits[*tx.ToForgeL1TxsNum] = append(forceExits[*tx.ToForgeL1TxsNum],
368 common.ExitInfo{
369 AccountIdx: tx.FromIdx,
370 Balance: tx.Amount,
371 })
372 }
373 }
374 for j := range block.Batches {
375 batch := &block.Batches[j]
376 if batch.L1Batch {
377 // Set BatchNum for forged L1UserTxs to til blocks
378 bn := batch.Batch.BatchNum
379 for k := range blocks {
380 block := &blocks[k]
381 for l := range block.L1UserTxs {
382 tx := &block.L1UserTxs[l]
383 if *tx.ToForgeL1TxsNum == openToForge {
384 tx.BatchNum = &bn
385 }
386 }
387 }
388 openToForge++
389 }
390
391 batch.Batch.EthBlockNum = block.Block.EthBlockNum
392 batch.Batch.ForgerAddr = bootCoordAddr // til doesn't fill the batch forger addr
393 if batch.L1Batch {
394 toForgeL1TxsNumCpy := toForgeL1TxsNum
395 batch.Batch.ForgeL1TxsNum = &toForgeL1TxsNumCpy // til doesn't fill the ForgeL1TxsNum
396 toForgeL1TxsNum++
397 }
398
399 batchNum := batch.Batch.BatchNum
400 for k := range batch.L1CoordinatorTxs {
401 tx := &batch.L1CoordinatorTxs[k]
402 tx.BatchNum = &batchNum
403 tx.EthBlockNum = batch.Batch.EthBlockNum
404 }
405 }
406 }
407
408 // Fill expected positions in L1CoordinatorTxs and L2Txs
409 for i := range blocks {
410 block := &blocks[i]
411 for j := range block.Batches {
412 batch := &block.Batches[j]
413 position := 0
414 if batch.L1Batch {
415 position = l1UserTxsLen[*batch.Batch.ForgeL1TxsNum]
416 }
417 for k := range batch.L1CoordinatorTxs {
418 tx := &batch.L1CoordinatorTxs[k]
419 tx.Position = position
420 position++
421 nTx, err := common.NewL1Tx(tx)
422 require.Nil(t, err)
423 *tx = *nTx
424 }
425 for k := range batch.L2Txs {
426 tx := &batch.L2Txs[k]
427 tx.Position = position
428 position++
429 nTx, err := common.NewL2Tx(tx)
430 require.Nil(t, err)
431 *tx = *nTx
432 }
433 }
434 }
435
436 // Fill ExitTree (only AccountIdx and Balance)
437 for i := range blocks {
438 block := &blocks[i]
439 for j := range block.Batches {
440 batch := &block.Batches[j]
441 if batch.L1Batch {
442 for forgeL1TxsNum, exits := range forceExits {
443 if forgeL1TxsNum == *batch.Batch.ForgeL1TxsNum {
444 batch.ExitTree = append(batch.ExitTree, exits...)
445 }
446 }
447 }
448 for k := range batch.L2Txs {
449 tx := &batch.L2Txs[k]
450 if tx.Type == common.TxTypeExit {
451 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
452 AccountIdx: tx.FromIdx,
453 Balance: tx.Amount,
454 })
455 }
456 }
457 }
458 }
459
460 //
461 // Sync to synchronize the current state from the test smart contracts,
462 // and check the outcome
463 //
464
465 // Block 2
466
467 syncBlock, discards, err = s.Sync2(ctx, nil)
468 require.Nil(t, err)
469 require.Nil(t, discards)
470 require.NotNil(t, syncBlock)
471 assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum)
472
473 checkSyncBlock(t, s, 2, &blocks[0], syncBlock)
474
475 // Block 3
476
477 syncBlock, discards, err = s.Sync2(ctx, nil)
478 require.Nil(t, err)
479 require.Nil(t, discards)
480 require.NotNil(t, syncBlock)
481 assert.Equal(t, int64(3), syncBlock.Block.EthBlockNum)
482
483 checkSyncBlock(t, s, 3, &blocks[1], syncBlock)
484
485 // TODO: Reorg will be properly tested once we have the mock ethClient implemented
486 /*
487 // Force a Reorg
488 lastSavedBlock, err := historyDB.GetLastBlock()
489 require.Nil(t, err)
490
491 lastSavedBlock.EthBlockNum++
492 err = historyDB.AddBlock(lastSavedBlock)
493 require.Nil(t, err)
494
495 lastSavedBlock.EthBlockNum++
496 err = historyDB.AddBlock(lastSavedBlock)
497 require.Nil(t, err)
498
499 log.Debugf("Wait for the blockchain to generate some blocks...")
500 time.Sleep(40 * time.Second)
501
502
503 err = s.Sync()
504 require.Nil(t, err)
505 */
506}