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 batch.Batch.NumAccounts = len(batch.CreatedAccounts)
127
128 // Test field by field to facilitate debugging of errors
129 assert.Equal(t, batch.L1CoordinatorTxs, syncBatch.L1CoordinatorTxs)
130 assert.Equal(t, batch.L2Txs, syncBatch.L2Txs)
131 // In exit tree, we only check AccountIdx and Balance, because
132 // it's what we have precomputed before.
133 for j := range batch.ExitTree {
134 exit := &batch.ExitTree[j]
135 assert.Equal(t, exit.AccountIdx, syncBatch.ExitTree[j].AccountIdx)
136 assert.Equal(t, exit.Balance, syncBatch.ExitTree[j].Balance)
137 *exit = syncBatch.ExitTree[j]
138 }
139 // We are collecting fees after blockNum=2 in 2 idxs
140 if block.Block.EthBlockNum > 2 {
141 // fmt.Printf("DBG collectedFees: %+v\n", syncBatch.Batch.CollectedFees)
142 assert.Equal(t, 2, len(syncBatch.Batch.CollectedFees))
143 }
144 batch.Batch.CollectedFees = syncBatch.Batch.CollectedFees
145 assert.Equal(t, batch.Batch, syncBatch.Batch)
146 assert.Equal(t, batch, syncBatch)
147 assert.Equal(t, &batch.Batch, dbBatch) //nolint:gosec
148
149 // Check L1CoordinatorTxs from DB
150 for _, tx := range batch.L1CoordinatorTxs {
151 var dbTx *common.L1Tx
152 // Find tx in DB output
153 for _, _dbTx := range dbL1CoordinatorTxs {
154 if *tx.BatchNum == *_dbTx.BatchNum &&
155 tx.Position == _dbTx.Position {
156 dbTx = new(common.L1Tx)
157 *dbTx = _dbTx
158 break
159 }
160 }
161 assert.Equal(t, &tx, dbTx) //nolint:gosec
162 }
163
164 // Check L2Txs from DB
165 for _, tx := range batch.L2Txs {
166 var dbTx *common.L2Tx
167 // Find tx in DB output
168 for _, _dbTx := range dbL2Txs {
169 if tx.BatchNum == _dbTx.BatchNum &&
170 tx.Position == _dbTx.Position {
171 dbTx = new(common.L2Tx)
172 *dbTx = _dbTx
173 break
174 }
175 }
176 assert.Equal(t, &tx, dbTx) //nolint:gosec
177 }
178
179 // Check Exits from DB
180 for _, exit := range batch.ExitTree {
181 var dbExit *common.ExitInfo
182 // Find exit in DB output
183 for _, _dbExit := range dbExits {
184 if exit.BatchNum == _dbExit.BatchNum &&
185 exit.AccountIdx == _dbExit.AccountIdx {
186 dbExit = new(common.ExitInfo)
187 *dbExit = _dbExit
188 break
189 }
190 }
191 // Compare MerkleProof in JSON because unmarshaled 0
192 // big.Int leaves the internal big.Int array at nil,
193 // and gives trouble when comparing big.Int with
194 // internal big.Int array != nil but empty.
195 mtp, err := json.Marshal(exit.MerkleProof)
196 require.Nil(t, err)
197 dbMtp, err := json.Marshal(dbExit.MerkleProof)
198 require.Nil(t, err)
199 assert.Equal(t, mtp, dbMtp)
200 dbExit.MerkleProof = exit.MerkleProof
201 assert.Equal(t, &exit, dbExit) //nolint:gosec
202 }
203 }
204}
205
206func TestSync(t *testing.T) {
207 //
208 // Setup
209 //
210
211 ctx := context.Background()
212 // Int State DB
213 dir, err := ioutil.TempDir("", "tmpdb")
214 require.Nil(t, err)
215 defer assert.Nil(t, os.RemoveAll(dir))
216
217 stateDB, err := statedb.NewStateDB(dir, statedb.TypeSynchronizer, 32)
218 assert.Nil(t, err)
219
220 // Init History DB
221 pass := os.Getenv("POSTGRES_PASS")
222 db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez")
223 require.Nil(t, err)
224 historyDB := historydb.NewHistoryDB(db)
225 // Clear DB
226 err = historyDB.Reorg(-1)
227 assert.Nil(t, err)
228
229 // Init eth client
230 var timer timer
231 clientSetup := test.NewClientSetupExample()
232 bootCoordAddr := clientSetup.AuctionVariables.BootCoordinator
233 client := test.NewClient(true, &timer, ðCommon.Address{}, clientSetup)
234
235 // Create Synchronizer
236 s, err := NewSynchronizer(client, historyDB, stateDB)
237 require.Nil(t, err)
238
239 //
240 // First Sync from an initial state
241 //
242
243 // Test Sync for rollup genesis block
244 syncBlock, discards, err := s.Sync2(ctx, nil)
245 require.Nil(t, err)
246 require.Nil(t, discards)
247 require.NotNil(t, syncBlock)
248 assert.Equal(t, int64(1), syncBlock.Block.EthBlockNum)
249 dbBlocks, err := s.historyDB.GetAllBlocks()
250 require.Nil(t, err)
251 assert.Equal(t, 1, len(dbBlocks))
252 assert.Equal(t, int64(1), dbBlocks[0].EthBlockNum)
253
254 // Sync again and expect no new blocks
255 syncBlock, discards, err = s.Sync2(ctx, nil)
256 require.Nil(t, err)
257 require.Nil(t, discards)
258 require.Nil(t, syncBlock)
259
260 //
261 // Generate blockchain and smart contract data, and fill the test smart contracts
262 //
263
264 // Generate blockchain data with til
265 set1 := `
266 Type: Blockchain
267
268 AddToken(1)
269 AddToken(2)
270 AddToken(3)
271
272 CreateAccountDeposit(1) C: 2000 // Idx=256+2=258
273 CreateAccountDeposit(2) A: 2000 // Idx=256+3=259
274 CreateAccountDeposit(1) D: 500 // Idx=256+4=260
275 CreateAccountDeposit(2) B: 500 // Idx=256+5=261
276 CreateAccountDeposit(2) C: 500 // Idx=256+6=262
277
278 CreateAccountCoordinator(1) A // Idx=256+0=256
279 CreateAccountCoordinator(1) B // Idx=256+1=257
280
281 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{5}
282 > batchL1 // forge defined L1UserTxs{5}, freeze L1UserTxs{nil}
283 > block // blockNum=2
284
285 CreateAccountDepositTransfer(1) E-A: 1000, 200 // Idx=256+7=263
286 ForceExit(1) A: 100
287 ForceTransfer(1) A-D: 100
288
289 Transfer(1) C-A: 100 (200)
290 Exit(1) D: 30 (200)
291
292 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{2}
293 > batchL1 // forge L1UserTxs{2}, freeze defined L1UserTxs{nil}
294 > block // blockNum=3
295
296 `
297 tc := til.NewContext(eth.RollupConstMaxL1UserTx)
298 blocks, err := tc.GenerateBlocks(set1)
299 require.Nil(t, err)
300 // Sanity check
301 require.Equal(t, 2, len(blocks))
302 // blocks 0 (blockNum=2)
303 i := 0
304 require.Equal(t, 2, int(blocks[i].Block.EthBlockNum))
305 require.Equal(t, 3, len(blocks[i].AddedTokens))
306 require.Equal(t, 5, len(blocks[i].L1UserTxs))
307 require.Equal(t, 2, len(blocks[i].Batches))
308 require.Equal(t, 2, len(blocks[i].Batches[0].L1CoordinatorTxs))
309 // blocks 1 (blockNum=3)
310 i = 1
311 require.Equal(t, 3, int(blocks[i].Block.EthBlockNum))
312 require.Equal(t, 3, len(blocks[i].L1UserTxs))
313 require.Equal(t, 2, len(blocks[i].Batches))
314 require.Equal(t, 2, len(blocks[i].Batches[0].L2Txs))
315
316 // Generate extra required data
317 for _, block := range blocks {
318 for _, token := range block.AddedTokens {
319 consts := eth.ERC20Consts{
320 Name: fmt.Sprintf("Token %d", token.TokenID),
321 Symbol: fmt.Sprintf("TK%d", token.TokenID),
322 Decimals: 18,
323 }
324 tokenConsts[token.TokenID] = consts
325 client.CtlAddERC20(token.EthAddr, consts)
326 }
327 }
328
329 // Add block data to the smart contracts
330 for _, block := range blocks {
331 for _, token := range block.AddedTokens {
332 _, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
333 require.Nil(t, err)
334 }
335 for _, tx := range block.L1UserTxs {
336 client.CtlSetAddr(tx.FromEthAddr)
337 _, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
338 uint32(tx.TokenID), int64(tx.ToIdx))
339 require.Nil(t, err)
340 }
341 client.CtlSetAddr(bootCoordAddr)
342 feeIdxCoordinator := []common.Idx{}
343 if block.Block.EthBlockNum > 2 {
344 // After blockNum=2 we have some accounts, use them as
345 // coordinator owned to receive fees.
346 feeIdxCoordinator = []common.Idx{common.Idx(256), common.Idx(259)}
347 }
348 for _, batch := range block.Batches {
349 _, err := client.RollupForgeBatch(ð.RollupForgeBatchArgs{
350 NewLastIdx: batch.Batch.LastIdx,
351 NewStRoot: batch.Batch.StateRoot,
352 NewExitRoot: batch.Batch.ExitRoot,
353 L1CoordinatorTxs: batch.L1CoordinatorTxs,
354 L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
355 L2TxsData: batch.L2Txs,
356 FeeIdxCoordinator: feeIdxCoordinator,
357 // Circuit selector
358 VerifierIdx: 0, // Intentionally empty
359 L1Batch: batch.L1Batch,
360 ProofA: [2]*big.Int{}, // Intentionally empty
361 ProofB: [2][2]*big.Int{}, // Intentionally empty
362 ProofC: [2]*big.Int{}, // Intentionally empty
363 })
364 require.Nil(t, err)
365 }
366 // Mine block and sync
367 client.CtlMineBlock()
368 }
369
370 // Fill extra fields not generated by til in til block
371 openToForge := int64(0)
372 toForgeL1TxsNum := int64(0)
373 l1UserTxsLen := map[int64]int{} // ForgeL1TxsNum -> len(L1UserTxs)
374 for i := range blocks {
375 block := &blocks[i]
376 // Count number of L1UserTxs in each queue, to figure out later
377 // position of L1CoordinatorTxs and L2Txs
378 for j := range block.L1UserTxs {
379 tx := &block.L1UserTxs[j]
380 l1UserTxsLen[*tx.ToForgeL1TxsNum]++
381 if tx.Type == common.TxTypeForceExit {
382 forceExits[*tx.ToForgeL1TxsNum] = append(forceExits[*tx.ToForgeL1TxsNum],
383 common.ExitInfo{
384 AccountIdx: tx.FromIdx,
385 Balance: tx.Amount,
386 })
387 }
388 }
389 for j := range block.Batches {
390 batch := &block.Batches[j]
391 if batch.L1Batch {
392 // Set BatchNum for forged L1UserTxs to til blocks
393 bn := batch.Batch.BatchNum
394 for k := range blocks {
395 block := &blocks[k]
396 for l := range block.L1UserTxs {
397 tx := &block.L1UserTxs[l]
398 if *tx.ToForgeL1TxsNum == openToForge {
399 tx.BatchNum = &bn
400 }
401 }
402 }
403 openToForge++
404 }
405
406 batch.Batch.EthBlockNum = block.Block.EthBlockNum
407 batch.Batch.ForgerAddr = bootCoordAddr // til doesn't fill the batch forger addr
408 if batch.L1Batch {
409 toForgeL1TxsNumCpy := toForgeL1TxsNum
410 batch.Batch.ForgeL1TxsNum = &toForgeL1TxsNumCpy // til doesn't fill the ForgeL1TxsNum
411 toForgeL1TxsNum++
412 }
413
414 batchNum := batch.Batch.BatchNum
415 for k := range batch.L1CoordinatorTxs {
416 tx := &batch.L1CoordinatorTxs[k]
417 tx.BatchNum = &batchNum
418 tx.EthBlockNum = batch.Batch.EthBlockNum
419 }
420 }
421 }
422
423 // Fill expected positions in L1CoordinatorTxs and L2Txs
424 for i := range blocks {
425 block := &blocks[i]
426 for j := range block.Batches {
427 batch := &block.Batches[j]
428 position := 0
429 if batch.L1Batch {
430 position = l1UserTxsLen[*batch.Batch.ForgeL1TxsNum]
431 }
432 for k := range batch.L1CoordinatorTxs {
433 tx := &batch.L1CoordinatorTxs[k]
434 tx.Position = position
435 position++
436 nTx, err := common.NewL1Tx(tx)
437 require.Nil(t, err)
438 *tx = *nTx
439 }
440 for k := range batch.L2Txs {
441 tx := &batch.L2Txs[k]
442 tx.Position = position
443 position++
444 nTx, err := common.NewL2Tx(tx)
445 require.Nil(t, err)
446 *tx = *nTx
447 }
448 }
449 }
450
451 // Fill ExitTree (only AccountIdx and Balance)
452 for i := range blocks {
453 block := &blocks[i]
454 for j := range block.Batches {
455 batch := &block.Batches[j]
456 if batch.L1Batch {
457 for forgeL1TxsNum, exits := range forceExits {
458 if forgeL1TxsNum == *batch.Batch.ForgeL1TxsNum {
459 batch.ExitTree = append(batch.ExitTree, exits...)
460 }
461 }
462 }
463 for k := range batch.L2Txs {
464 tx := &batch.L2Txs[k]
465 if tx.Type == common.TxTypeExit {
466 batch.ExitTree = append(batch.ExitTree, common.ExitInfo{
467 AccountIdx: tx.FromIdx,
468 Balance: tx.Amount,
469 })
470 }
471 }
472 }
473 }
474
475 //
476 // Sync to synchronize the current state from the test smart contracts,
477 // and check the outcome
478 //
479
480 // Block 2
481
482 syncBlock, discards, err = s.Sync2(ctx, nil)
483 require.Nil(t, err)
484 require.Nil(t, discards)
485 require.NotNil(t, syncBlock)
486 assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum)
487
488 checkSyncBlock(t, s, 2, &blocks[0], syncBlock)
489
490 // Block 3
491
492 syncBlock, discards, err = s.Sync2(ctx, nil)
493 require.Nil(t, err)
494 require.Nil(t, discards)
495 require.NotNil(t, syncBlock)
496 assert.Equal(t, int64(3), syncBlock.Block.EthBlockNum)
497
498 checkSyncBlock(t, s, 3, &blocks[1], syncBlock)
499
500 // TODO: Reorg will be properly tested once we have the mock ethClient implemented
501 /*
502 // Force a Reorg
503 lastSavedBlock, err := historyDB.GetLastBlock()
504 require.Nil(t, err)
505
506 lastSavedBlock.EthBlockNum++
507 err = historyDB.AddBlock(lastSavedBlock)
508 require.Nil(t, err)
509
510 lastSavedBlock.EthBlockNum++
511 err = historyDB.AddBlock(lastSavedBlock)
512 require.Nil(t, err)
513
514 log.Debugf("Wait for the blockchain to generate some blocks...")
515 time.Sleep(40 * time.Second)
516
517
518 err = s.Sync()
519 require.Nil(t, err)
520 */
521}