arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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, &ethCommon.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(&eth.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}