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
 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, &ethCommon.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(&eth.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}