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{}
26
27type timer struct {
28 time int64
29}
30
31func (t *timer) Time() int64 {
32 currentTime := t.time
33 t.time++
34 return currentTime
35}
36
37// Check Sync output and HistoryDB state against expected values generated by
38// til
39func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBlock *common.BlockData) {
40 // Check Blocks
41 dbBlocks, err := s.historyDB.GetAllBlocks()
42 require.Nil(t, err)
43 dbBlocks = dbBlocks[1:] // ignore block 0, added by default in the DB
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.Rollup.AddedTokens), len(syncBlock.Rollup.AddedTokens))
51 dbTokens, err := s.historyDB.GetAllTokens()
52 require.Nil(t, err)
53 dbTokens = dbTokens[1:] // ignore token 0, added by default in the DB
54 for i, token := range block.Rollup.AddedTokens {
55 dbToken := dbTokens[i]
56 syncToken := syncBlock.Rollup.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.Rollup.L1UserTxs), len(syncBlock.Rollup.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.Rollup.L1UserTxs {
80 syncBlock.Rollup.L1UserTxs[i].BatchNum = block.Rollup.L1UserTxs[i].BatchNum
81 }
82 assert.Equal(t, block.Rollup.L1UserTxs, syncBlock.Rollup.L1UserTxs)
83 for _, tx := range block.Rollup.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.Rollup.Batches), len(syncBlock.Rollup.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 dbL2Txs, err := s.historyDB.GetAllL2Txs()
105 require.Nil(t, err)
106 dbExits, err := s.historyDB.GetAllExits()
107 require.Nil(t, err)
108 // dbL1CoordinatorTxs := []common.L1Tx{}
109 for i, batch := range block.Rollup.Batches {
110 var dbBatch *common.Batch
111 // Find batch in DB output
112 for _, _dbBatch := range dbBatches {
113 if batch.Batch.BatchNum == _dbBatch.BatchNum {
114 dbBatch = new(common.Batch)
115 *dbBatch = _dbBatch
116 break
117 }
118 }
119 syncBatch := syncBlock.Rollup.Batches[i]
120
121 // We don't care about TotalFeesUSD. Use the syncBatch that
122 // has a TotalFeesUSD inserted by the HistoryDB
123 batch.Batch.TotalFeesUSD = syncBatch.Batch.TotalFeesUSD
124 assert.Equal(t, batch.CreatedAccounts, syncBatch.CreatedAccounts)
125 batch.Batch.NumAccounts = len(batch.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 test.WipeDB(historyDB.DB())
220
221 // Init eth client
222 var timer timer
223 clientSetup := test.NewClientSetupExample()
224 bootCoordAddr := clientSetup.AuctionVariables.BootCoordinator
225 client := test.NewClient(true, &timer, ðCommon.Address{}, clientSetup)
226
227 // Create Synchronizer
228 s, err := NewSynchronizer(client, historyDB, stateDB, Config{
229 StartBlockNum: ConfigStartBlockNum{
230 Rollup: 1,
231 Auction: 1,
232 WDelayer: 1,
233 },
234 InitialVariables: SCVariables{
235 Rollup: *clientSetup.RollupVariables,
236 Auction: *clientSetup.AuctionVariables,
237 WDelayer: *clientSetup.WDelayerVariables,
238 },
239 })
240 require.Nil(t, err)
241
242 //
243 // First Sync from an initial state
244 //
245
246 // Test Sync for rollup genesis block
247 syncBlock, discards, err := s.Sync2(ctx, nil)
248 require.Nil(t, err)
249 require.Nil(t, discards)
250 require.NotNil(t, syncBlock)
251 assert.Equal(t, int64(1), syncBlock.Block.EthBlockNum)
252 dbBlocks, err := s.historyDB.GetAllBlocks()
253 require.Nil(t, err)
254 assert.Equal(t, 2, len(dbBlocks))
255 assert.Equal(t, int64(1), dbBlocks[1].EthBlockNum)
256
257 // Sync again and expect no new blocks
258 syncBlock, discards, err = s.Sync2(ctx, nil)
259 require.Nil(t, err)
260 require.Nil(t, discards)
261 require.Nil(t, syncBlock)
262
263 //
264 // Generate blockchain and smart contract data, and fill the test smart contracts
265 //
266
267 // Generate blockchain data with til
268 set1 := `
269 Type: Blockchain
270
271 AddToken(1)
272 AddToken(2)
273 AddToken(3)
274
275 CreateAccountDeposit(1) C: 2000 // Idx=256+2=258
276 CreateAccountDeposit(2) A: 2000 // Idx=256+3=259
277 CreateAccountDeposit(1) D: 500 // Idx=256+4=260
278 CreateAccountDeposit(2) B: 500 // Idx=256+5=261
279 CreateAccountDeposit(2) C: 500 // Idx=256+6=262
280
281 CreateAccountCoordinator(1) A // Idx=256+0=256
282 CreateAccountCoordinator(1) B // Idx=256+1=257
283
284 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{5}
285 > batchL1 // forge defined L1UserTxs{5}, freeze L1UserTxs{nil}
286 > block // blockNum=2
287
288 CreateAccountDepositTransfer(1) E-A: 1000, 200 // Idx=256+7=263
289 ForceExit(1) A: 100
290 ForceExit(1) B: 80
291 ForceTransfer(1) A-D: 100
292
293 Transfer(1) C-A: 100 (200)
294 Exit(1) C: 50 (200)
295 Exit(1) D: 30 (200)
296
297 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{3}
298 > batchL1 // forge L1UserTxs{3}, freeze defined L1UserTxs{nil}
299 > block // blockNum=3
300 `
301 tc := til.NewContext(common.RollupConstMaxL1UserTx)
302 tilCfgExtra := til.ConfigExtra{
303 BootCoordAddr: bootCoordAddr,
304 CoordUser: "A",
305 }
306 blocks, err := tc.GenerateBlocks(set1)
307 require.Nil(t, err)
308 // Sanity check
309 require.Equal(t, 2, len(blocks))
310 // blocks 0 (blockNum=2)
311 i := 0
312 require.Equal(t, 2, int(blocks[i].Block.EthBlockNum))
313 require.Equal(t, 3, len(blocks[i].Rollup.AddedTokens))
314 require.Equal(t, 5, len(blocks[i].Rollup.L1UserTxs))
315 require.Equal(t, 2, len(blocks[i].Rollup.Batches))
316 require.Equal(t, 2, len(blocks[i].Rollup.Batches[0].L1CoordinatorTxs))
317 // blocks 1 (blockNum=3)
318 i = 1
319 require.Equal(t, 3, int(blocks[i].Block.EthBlockNum))
320 require.Equal(t, 4, len(blocks[i].Rollup.L1UserTxs))
321 require.Equal(t, 2, len(blocks[i].Rollup.Batches))
322 require.Equal(t, 3, len(blocks[i].Rollup.Batches[0].L2Txs))
323
324 // Generate extra required data
325 for _, block := range blocks {
326 for _, token := range block.Rollup.AddedTokens {
327 consts := eth.ERC20Consts{
328 Name: fmt.Sprintf("Token %d", token.TokenID),
329 Symbol: fmt.Sprintf("TK%d", token.TokenID),
330 Decimals: 18,
331 }
332 tokenConsts[token.TokenID] = consts
333 client.CtlAddERC20(token.EthAddr, consts)
334 }
335 }
336
337 err = tc.FillBlocksExtra(blocks, &tilCfgExtra)
338 assert.Nil(t, err)
339 tc.FillBlocksL1UserTxsBatchNum(blocks)
340
341 // Add block data to the smart contracts
342 for _, block := range blocks {
343 for _, token := range block.Rollup.AddedTokens {
344 _, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
345 require.Nil(t, err)
346 }
347 for _, tx := range block.Rollup.L1UserTxs {
348 client.CtlSetAddr(tx.FromEthAddr)
349 _, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
350 uint32(tx.TokenID), int64(tx.ToIdx))
351 require.Nil(t, err)
352 }
353 client.CtlSetAddr(bootCoordAddr)
354 // feeIdxCoordinator := []common.Idx{}
355 // if block.Block.EthBlockNum > 2 {
356 // // After blockNum=2 we have some accounts, use them as
357 // // coordinator owned to receive fees.
358 // feeIdxCoordinator = []common.Idx{common.Idx(256), common.Idx(259)}
359 // }
360 for _, batch := range block.Rollup.Batches {
361 _, err := client.RollupForgeBatch(ð.RollupForgeBatchArgs{
362 NewLastIdx: batch.Batch.LastIdx,
363 NewStRoot: batch.Batch.StateRoot,
364 NewExitRoot: batch.Batch.ExitRoot,
365 L1CoordinatorTxs: batch.L1CoordinatorTxs,
366 L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
367 L2TxsData: batch.L2Txs,
368 FeeIdxCoordinator: batch.Batch.FeeIdxsCoordinator,
369 // Circuit selector
370 VerifierIdx: 0, // Intentionally empty
371 L1Batch: batch.L1Batch,
372 ProofA: [2]*big.Int{}, // Intentionally empty
373 ProofB: [2][2]*big.Int{}, // Intentionally empty
374 ProofC: [2]*big.Int{}, // Intentionally empty
375 })
376 require.Nil(t, err)
377 }
378 // Mine block and sync
379 client.CtlMineBlock()
380 }
381
382 //
383 // Sync to synchronize the current state from the test smart contracts,
384 // and check the outcome
385 //
386
387 // Block 2
388
389 syncBlock, discards, err = s.Sync2(ctx, nil)
390 require.Nil(t, err)
391 require.Nil(t, discards)
392 require.NotNil(t, syncBlock)
393 assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum)
394
395 checkSyncBlock(t, s, 2, &blocks[0], syncBlock)
396
397 // Block 3
398
399 syncBlock, discards, err = s.Sync2(ctx, nil)
400 require.Nil(t, err)
401 require.Nil(t, discards)
402 require.NotNil(t, syncBlock)
403 assert.Equal(t, int64(3), syncBlock.Block.EthBlockNum)
404
405 checkSyncBlock(t, s, 3, &blocks[1], syncBlock)
406
407 // Block 4
408 // Generate 2 withdraws manually
409 _, err = client.RollupWithdrawMerkleProof(tc.Users["A"].BJJ.Public(), 1, 4, 256, big.NewInt(100), []*big.Int{}, true)
410 require.Nil(t, err)
411 _, err = client.RollupWithdrawMerkleProof(tc.Users["C"].BJJ.Public(), 1, 3, 258, big.NewInt(50), []*big.Int{}, false)
412 require.Nil(t, err)
413 client.CtlMineBlock()
414
415 syncBlock, discards, err = s.Sync2(ctx, nil)
416 require.Nil(t, err)
417 require.Nil(t, discards)
418 require.NotNil(t, syncBlock)
419 assert.Equal(t, int64(4), syncBlock.Block.EthBlockNum)
420 dbExits, err := s.historyDB.GetAllExits()
421 require.Nil(t, err)
422 foundA1, foundC1 := false, false
423 for _, exit := range dbExits {
424 if exit.AccountIdx == 256 && exit.BatchNum == 4 {
425 foundA1 = true
426 assert.Equal(t, int64(4), *exit.InstantWithdrawn)
427 }
428 if exit.AccountIdx == 258 && exit.BatchNum == 3 {
429 foundC1 = true
430 assert.Equal(t, int64(4), *exit.DelayedWithdrawRequest)
431 }
432 }
433 assert.True(t, foundA1)
434 assert.True(t, foundC1)
435
436 // Block 5
437 // Update variables manually
438 rollupVars, auctionVars, wDelayerVars, err := s.historyDB.GetSCVars()
439 require.Nil(t, err)
440 rollupVars.ForgeL1L2BatchTimeout = 42
441 _, err = client.RollupUpdateForgeL1L2BatchTimeout(rollupVars.ForgeL1L2BatchTimeout)
442 require.Nil(t, err)
443
444 auctionVars.OpenAuctionSlots = 17
445 _, err = client.AuctionSetOpenAuctionSlots(auctionVars.OpenAuctionSlots)
446 require.Nil(t, err)
447
448 wDelayerVars.WithdrawalDelay = 99
449 _, err = client.WDelayerChangeWithdrawalDelay(wDelayerVars.WithdrawalDelay)
450 require.Nil(t, err)
451
452 client.CtlMineBlock()
453
454 syncBlock, discards, err = s.Sync2(ctx, nil)
455 require.Nil(t, err)
456 require.Nil(t, discards)
457 require.NotNil(t, syncBlock)
458 assert.Equal(t, int64(5), syncBlock.Block.EthBlockNum)
459
460 dbRollupVars, dbAuctionVars, dbWDelayerVars, err := s.historyDB.GetSCVars()
461 require.Nil(t, err)
462 // Set EthBlockNum for Vars to the blockNum in which they were updated (should be 5)
463 rollupVars.EthBlockNum = syncBlock.Block.EthBlockNum
464 auctionVars.EthBlockNum = syncBlock.Block.EthBlockNum
465 wDelayerVars.EthBlockNum = syncBlock.Block.EthBlockNum
466 assert.Equal(t, rollupVars, dbRollupVars)
467 assert.Equal(t, auctionVars, dbAuctionVars)
468 assert.Equal(t, wDelayerVars, dbWDelayerVars)
469
470 // TODO: Reorg will be properly tested once we have the mock ethClient implemented
471 /*
472 // Force a Reorg
473 lastSavedBlock, err := historyDB.GetLastBlock()
474 require.Nil(t, err)
475
476 lastSavedBlock.EthBlockNum++
477 err = historyDB.AddBlock(lastSavedBlock)
478 require.Nil(t, err)
479
480 lastSavedBlock.EthBlockNum++
481 err = historyDB.AddBlock(lastSavedBlock)
482 require.Nil(t, err)
483
484 log.Debugf("Wait for the blockchain to generate some blocks...")
485 time.Sleep(40 * time.Second)
486
487
488 err = s.Sync()
489 require.Nil(t, err)
490 */
491}