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