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 ForceTransfer(1) A-D: 100
291
292 Transfer(1) C-A: 100 (200)
293 Exit(1) D: 30 (200)
294
295 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs{2}
296 > batchL1 // forge L1UserTxs{2}, freeze defined L1UserTxs{nil}
297 > block // blockNum=3
298 `
299 tc := til.NewContext(common.RollupConstMaxL1UserTx)
300 tilCfgExtra := til.ConfigExtra{
301 BootCoordAddr: bootCoordAddr,
302 CoordUser: "A",
303 }
304 blocks, err := tc.GenerateBlocks(set1)
305 require.Nil(t, err)
306 // Sanity check
307 require.Equal(t, 2, len(blocks))
308 // blocks 0 (blockNum=2)
309 i := 0
310 require.Equal(t, 2, int(blocks[i].Block.EthBlockNum))
311 require.Equal(t, 3, len(blocks[i].Rollup.AddedTokens))
312 require.Equal(t, 5, len(blocks[i].Rollup.L1UserTxs))
313 require.Equal(t, 2, len(blocks[i].Rollup.Batches))
314 require.Equal(t, 2, len(blocks[i].Rollup.Batches[0].L1CoordinatorTxs))
315 // blocks 1 (blockNum=3)
316 i = 1
317 require.Equal(t, 3, int(blocks[i].Block.EthBlockNum))
318 require.Equal(t, 3, len(blocks[i].Rollup.L1UserTxs))
319 require.Equal(t, 2, len(blocks[i].Rollup.Batches))
320 require.Equal(t, 2, len(blocks[i].Rollup.Batches[0].L2Txs))
321
322 // Generate extra required data
323 for _, block := range blocks {
324 for _, token := range block.Rollup.AddedTokens {
325 consts := eth.ERC20Consts{
326 Name: fmt.Sprintf("Token %d", token.TokenID),
327 Symbol: fmt.Sprintf("TK%d", token.TokenID),
328 Decimals: 18,
329 }
330 tokenConsts[token.TokenID] = consts
331 client.CtlAddERC20(token.EthAddr, consts)
332 }
333 }
334
335 err = tc.FillBlocksExtra(blocks, &tilCfgExtra)
336 assert.Nil(t, err)
337
338 // Add block data to the smart contracts
339 for _, block := range blocks {
340 for _, token := range block.Rollup.AddedTokens {
341 _, err := client.RollupAddTokenSimple(token.EthAddr, clientSetup.RollupVariables.FeeAddToken)
342 require.Nil(t, err)
343 }
344 for _, tx := range block.Rollup.L1UserTxs {
345 client.CtlSetAddr(tx.FromEthAddr)
346 _, err := client.RollupL1UserTxERC20ETH(tx.FromBJJ, int64(tx.FromIdx), tx.LoadAmount, tx.Amount,
347 uint32(tx.TokenID), int64(tx.ToIdx))
348 require.Nil(t, err)
349 }
350 client.CtlSetAddr(bootCoordAddr)
351 // feeIdxCoordinator := []common.Idx{}
352 // if block.Block.EthBlockNum > 2 {
353 // // After blockNum=2 we have some accounts, use them as
354 // // coordinator owned to receive fees.
355 // feeIdxCoordinator = []common.Idx{common.Idx(256), common.Idx(259)}
356 // }
357 for _, batch := range block.Rollup.Batches {
358 _, err := client.RollupForgeBatch(ð.RollupForgeBatchArgs{
359 NewLastIdx: batch.Batch.LastIdx,
360 NewStRoot: batch.Batch.StateRoot,
361 NewExitRoot: batch.Batch.ExitRoot,
362 L1CoordinatorTxs: batch.L1CoordinatorTxs,
363 L1CoordinatorTxsAuths: [][]byte{}, // Intentionally empty
364 L2TxsData: batch.L2Txs,
365 FeeIdxCoordinator: batch.Batch.FeeIdxsCoordinator,
366 // Circuit selector
367 VerifierIdx: 0, // Intentionally empty
368 L1Batch: batch.L1Batch,
369 ProofA: [2]*big.Int{}, // Intentionally empty
370 ProofB: [2][2]*big.Int{}, // Intentionally empty
371 ProofC: [2]*big.Int{}, // Intentionally empty
372 })
373 require.Nil(t, err)
374 }
375 // Mine block and sync
376 client.CtlMineBlock()
377 }
378
379 //
380 // Sync to synchronize the current state from the test smart contracts,
381 // and check the outcome
382 //
383
384 // Block 2
385
386 syncBlock, discards, err = s.Sync2(ctx, nil)
387 require.Nil(t, err)
388 require.Nil(t, discards)
389 require.NotNil(t, syncBlock)
390 assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum)
391
392 checkSyncBlock(t, s, 2, &blocks[0], syncBlock)
393
394 // Block 3
395
396 syncBlock, discards, err = s.Sync2(ctx, nil)
397 require.Nil(t, err)
398 require.Nil(t, discards)
399 require.NotNil(t, syncBlock)
400 assert.Equal(t, int64(3), syncBlock.Block.EthBlockNum)
401
402 checkSyncBlock(t, s, 3, &blocks[1], syncBlock)
403
404 // TODO: Reorg will be properly tested once we have the mock ethClient implemented
405 /*
406 // Force a Reorg
407 lastSavedBlock, err := historyDB.GetLastBlock()
408 require.Nil(t, err)
409
410 lastSavedBlock.EthBlockNum++
411 err = historyDB.AddBlock(lastSavedBlock)
412 require.Nil(t, err)
413
414 lastSavedBlock.EthBlockNum++
415 err = historyDB.AddBlock(lastSavedBlock)
416 require.Nil(t, err)
417
418 log.Debugf("Wait for the blockchain to generate some blocks...")
419 time.Sleep(40 * time.Second)
420
421
422 err = s.Sync()
423 require.Nil(t, err)
424 */
425}