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