From 558de2737ee4df3d848cf975475c37bdfb42643d Mon Sep 17 00:00:00 2001 From: Eduard S Date: Tue, 27 Oct 2020 12:49:07 +0100 Subject: [PATCH] Test L2Txs, ExitTree in synchronizer --- common/l2tx.go | 20 +- common/tx.go | 2 +- db/historydb/historydb.go | 42 +++- db/historydb/historydb_test.go | 6 - db/statedb/txprocessors.go | 36 +-- synchronizer/synchronizer.go | 64 +++--- synchronizer/synchronizer_test.go | 365 ++++++++++++++++++++++-------- test/til/txs.go | 29 ++- 8 files changed, 398 insertions(+), 166 deletions(-) diff --git a/common/l2tx.go b/common/l2tx.go index 1bd163b..a2bb296 100644 --- a/common/l2tx.go +++ b/common/l2tx.go @@ -8,16 +8,16 @@ import ( // L2Tx is a struct that represents an already forged L2 tx type L2Tx struct { // Stored in DB: mandatory fileds - TxID TxID - BatchNum BatchNum // batchNum in which this tx was forged. - Position int - FromIdx Idx - ToIdx Idx - Amount *big.Int - Fee FeeSelector - Nonce Nonce - Type TxType - EthBlockNum int64 // Ethereum Block Number in which this L2Tx was added to the queue + TxID TxID `meddler:"id"` + BatchNum BatchNum `meddler:"batch_num"` // batchNum in which this tx was forged. + Position int `meddler:"position"` + FromIdx Idx `meddler:"from_idx"` + ToIdx Idx `meddler:"to_idx"` + Amount *big.Int `meddler:"amount,bigint"` + Fee FeeSelector `meddler:"fee"` + Nonce Nonce `meddler:"nonce"` + Type TxType `meddler:"type"` + EthBlockNum int64 `meddler:"eth_block_num"` // Ethereum Block Number in which this L2Tx was added to the queue } // NewL2Tx returns the given L2Tx with the TxId & Type parameters calculated diff --git a/common/tx.go b/common/tx.go index 8e5cba5..cd0311e 100644 --- a/common/tx.go +++ b/common/tx.go @@ -168,7 +168,7 @@ func (tx *Tx) String() string { tx.Type == TxTypeCreateAccountDepositTransfer { fmt.Fprintf(buf, "Fee: %d, ", tx.Fee) } - fmt.Fprintf(buf, "TokenID: %d\n", tx.TokenID) + fmt.Fprintf(buf, "TokenID: %d", tx.TokenID) return buf.String() } diff --git a/db/historydb/historydb.go b/db/historydb/historydb.go index 920334d..1440eab 100644 --- a/db/historydb/historydb.go +++ b/db/historydb/historydb.go @@ -272,7 +272,9 @@ func (hdb *HistoryDB) GetAllBatches() ([]common.Batch, error) { var batches []*common.Batch err := meddler.QueryAll( hdb.db, &batches, - "SELECT * FROM batch;", + `SELECT batch.batch_num, batch.eth_block_num, batch.forger_addr, batch.fees_collected, + batch.state_root, batch.num_accounts, batch.last_idx, batch.exit_root, + batch.forge_l1_txs_num, batch.slot_num, batch.total_fees_usd FROM batch;`, ) return db.SlicePtrsToSlice(batches).([]common.Batch), err } @@ -796,6 +798,18 @@ func (hdb *HistoryDB) GetHistoryTxs( }, nil } +// GetAllExits returns all exit from the DB +func (hdb *HistoryDB) GetAllExits() ([]common.ExitInfo, error) { + var exits []*common.ExitInfo + err := meddler.QueryAll( + hdb.db, &exits, + `SELECT exit_tree.batch_num, exit_tree.account_idx, exit_tree.merkle_proof, + exit_tree.balance, exit_tree.instant_withdrawn, exit_tree.delayed_withdraw_request, + exit_tree.delayed_withdrawn FROM exit_tree;`, + ) + return db.SlicePtrsToSlice(exits).([]common.ExitInfo), err +} + // GetExit returns a exit from the DB func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) { exit := &HistoryExit{} @@ -931,6 +945,32 @@ func (hdb *HistoryDB) GetAllL1UserTxs() ([]common.L1Tx, error) { return db.SlicePtrsToSlice(txs).([]common.L1Tx), err } +// GetAllL1CoordinatorTxs returns all L1CoordinatorTxs from the DB +func (hdb *HistoryDB) GetAllL1CoordinatorTxs() ([]common.L1Tx, error) { + var txs []*common.L1Tx + err := meddler.QueryAll( + hdb.db, &txs, + `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin, + tx.from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id, tx.amount, + tx.load_amount, tx.eth_block_num, tx.type, tx.batch_num + FROM tx WHERE is_l1 = TRUE AND user_origin = FALSE;`, + ) + return db.SlicePtrsToSlice(txs).([]common.L1Tx), err +} + +// GetAllL2Txs returns all L2Txs from the DB +func (hdb *HistoryDB) GetAllL2Txs() ([]common.L2Tx, error) { + var txs []*common.L2Tx + err := meddler.QueryAll( + hdb.db, &txs, + `SELECT tx.id, tx.batch_num, tx.position, + tx.from_idx, tx.to_idx, tx.amount, tx.fee, tx.nonce, + tx.type, tx.eth_block_num + FROM tx WHERE is_l1 = FALSE;`, + ) + return db.SlicePtrsToSlice(txs).([]common.L2Tx), err +} + // GetL1UserTxs gets L1 User Txs to be forged in the L1Batch with toForgeL1TxsNum. func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]common.L1Tx, error) { var txs []*common.L1Tx diff --git a/db/historydb/historydb_test.go b/db/historydb/historydb_test.go index db2ecfc..133920b 100644 --- a/db/historydb/historydb_test.go +++ b/db/historydb/historydb_test.go @@ -1,7 +1,6 @@ package historydb import ( - "fmt" "math" "math/big" "os" @@ -409,11 +408,6 @@ func TestGetL1UserTxs(t *testing.T) { toForgeL1TxsNum := int64(1) for i := range blocks { - fmt.Printf("DBG %+v\n", blocks[i]) - // fmt.Printf("DBG Batches %+v\n", blocks[i].Batches) - // for _, l1Tx := range blocks[i].L1UserTxs { - // fmt.Printf("DBG l1UserTx %+v\n", l1Tx) - // } err = historyDB.AddBlockSCData(&blocks[i]) require.Nil(t, err) } diff --git a/db/statedb/txprocessors.go b/db/statedb/txprocessors.go index efcd324..7c06459 100644 --- a/db/statedb/txprocessors.go +++ b/db/statedb/txprocessors.go @@ -429,10 +429,10 @@ func (s *StateDB) processL2Tx(coordIdxsMap map[common.TokenID]common.Idx, exitTr // as type==TypeSynchronizer, always tx.ToIdx!=0 acc, err := s.GetAccount(tx.FromIdx) if err != nil { - log.Error(err) + log.Errorw("GetAccount", "fromIdx", tx.FromIdx, "err", err) return nil, nil, false, err } - tx.Nonce = acc.Nonce + tx.Nonce = acc.Nonce + 1 tx.TokenID = acc.TokenID } @@ -592,14 +592,14 @@ func (s *StateDB) applyTransfer(coordIdxsMap map[common.TokenID]common.Idx, tx c // send the fee to the Idx of the Coordinator for the TokenID accCoord, err := s.GetAccount(coordIdxsMap[tx.TokenID]) if err != nil { - log.Errorf("applyTransfer error: Tx=%s, error: %s", tx.String(), err) - return err - } - accCoord.Balance = new(big.Int).Add(accCoord.Balance, fee) - _, err = s.UpdateAccount(coordIdxsMap[tx.TokenID], accCoord) - if err != nil { - log.Error(err) - return err + log.Debugw("No coord Idx to receive fee", "tx", tx) + } else { + accCoord.Balance = new(big.Int).Add(accCoord.Balance, fee) + _, err = s.UpdateAccount(coordIdxsMap[tx.TokenID], accCoord) + if err != nil { + log.Error(err) + return err + } } } @@ -726,14 +726,14 @@ func (s *StateDB) applyExit(coordIdxsMap map[common.TokenID]common.Idx, exitTree // send the fee to the Idx of the Coordinator for the TokenID accCoord, err := s.GetAccount(coordIdxsMap[tx.TokenID]) if err != nil { - log.Errorf("applyExit error: Tx=%s, error: %s", tx.String(), err) - return nil, false, err - } - accCoord.Balance = new(big.Int).Add(accCoord.Balance, fee) - _, err = s.UpdateAccount(coordIdxsMap[tx.TokenID], accCoord) - if err != nil { - log.Error(err) - return nil, false, err + log.Debugw("No coord Idx to receive fee", "tx", tx) + } else { + accCoord.Balance = new(big.Int).Add(accCoord.Balance, fee) + _, err = s.UpdateAccount(coordIdxsMap[tx.TokenID], accCoord) + if err != nil { + log.Error(err) + return nil, false, err + } } } diff --git a/synchronizer/synchronizer.go b/synchronizer/synchronizer.go index 3eb6e30..c0f9498 100644 --- a/synchronizer/synchronizer.go +++ b/synchronizer/synchronizer.go @@ -3,7 +3,6 @@ package synchronizer import ( "context" "database/sql" - "fmt" "github.com/ethereum/go-ethereum" "github.com/hermeznetwork/hermez-node/common" @@ -344,7 +343,6 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { } batchNum := common.BatchNum(evtForgeBatch.BatchNum) - nextForgeL1TxsNumCpy := nextForgeL1TxsNum var l1UserTxs []common.L1Tx // Check if this is a L1Batch to get L1 Tx from it if forgeBatchArgs.L1Batch { @@ -353,28 +351,22 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { // that stateDB can process them. // First try to find them in HistoryDB. - l1UserTxs, err := s.historyDB.GetL1UserTxs(nextForgeL1TxsNumCpy) - if len(l1UserTxs) == 0 { - // If not found in the DB, try to find them in - // this block. This could happen because in a - // block there could be multiple batches with - // L1Batch = true (although it's a very rare - // case). - // If not found in the DB and the block doesn't - // contain the l1UserTxs, it means that the - // L1UserTxs queue with toForgeL1TxsNum was - // closed empty, so we leave `l1UserTxs` as an - // empty slice. - for _, l1UserTx := range rollupData.l1UserTxs { - if *l1UserTx.ToForgeL1TxsNum == nextForgeL1TxsNumCpy { - l1UserTxs = append(l1UserTxs, l1UserTx) - } - } - } + l1UserTxs, err = s.historyDB.GetL1UserTxs(nextForgeL1TxsNum) if err != nil { return nil, err } - nextForgeL1TxsNum++ + // Apart from the DB, try to find them in this block. + // This could happen because in a block there could be + // multiple batches with L1Batch = true (although it's + // a very rare case). If not found in the DB and the + // block doesn't contain the l1UserTxs, it means that + // the L1UserTxs queue with toForgeL1TxsNum was closed + // empty, so we leave `l1UserTxs` as an empty slice. + for _, l1UserTx := range rollupData.l1UserTxs { + if *l1UserTx.ToForgeL1TxsNum == nextForgeL1TxsNum { + l1UserTxs = append(l1UserTxs, l1UserTx) + } + } position = len(l1UserTxs) } @@ -393,7 +385,7 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { batchData.L1CoordinatorTxs = append(batchData.L1CoordinatorTxs, *l1Tx) position++ - fmt.Println("DGB l1coordtx") + // fmt.Println("DGB l1coordtx") } // Insert all the txs forged in this batch (l1UserTxs, @@ -401,7 +393,6 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { // processed. poolL2Txs := common.L2TxsToPoolL2Txs(forgeBatchArgs.L2TxsData) // TODO: This is a big ugly, find a better way - // TODO: Get createdAccounts from ProcessTxs() // TODO: Get CollectedFees from ProcessTxs() // TODO: Pass forgeBatchArgs.FeeIdxCoordinator to ProcessTxs() // ProcessTxs updates poolL2Txs adding: Nonce, TokenID @@ -410,26 +401,37 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { return nil, err } + // Set batchNum in exits + for i := range ptOut.ExitInfos { + exit := &ptOut.ExitInfos[i] + exit.BatchNum = batchNum + } + batchData.ExitTree = ptOut.ExitInfos + l2Txs, err := common.PoolL2TxsToL2Txs(poolL2Txs) // TODO: This is a big uggly, find a better way if err != nil { return nil, err } for i := range l2Txs { - _l2Tx := l2Txs[i] - _l2Tx.Position = position - _l2Tx.EthBlockNum = blockNum - _l2Tx.BatchNum = batchNum - l2Tx, err := common.NewL2Tx(&_l2Tx) + tx := &l2Txs[i] + tx.Position = position + tx.EthBlockNum = blockNum + tx.BatchNum = batchNum + nTx, err := common.NewL2Tx(tx) if err != nil { return nil, err } - batchData.L2Txs = append(batchData.L2Txs, *l2Tx) + batchData.L2Txs = append(batchData.L2Txs, *nTx) position++ } - batchData.ExitTree = ptOut.ExitInfos + for i := range ptOut.CreatedAccounts { + createdAccount := &ptOut.CreatedAccounts[i] + createdAccount.BatchNum = batchNum + } + batchData.CreatedAccounts = ptOut.CreatedAccounts slotNum := int64(0) if ethBlock.EthBlockNum >= s.auctionConstants.GenesisBlockNum { @@ -449,9 +451,11 @@ func (s *Synchronizer) rollupSync(ethBlock *common.Block) (*rollupData, error) { ExitRoot: forgeBatchArgs.NewExitRoot, SlotNum: slotNum, } + nextForgeL1TxsNumCpy := nextForgeL1TxsNum if forgeBatchArgs.L1Batch { batch.ForgeL1TxsNum = &nextForgeL1TxsNumCpy batchData.L1Batch = true + nextForgeL1TxsNum++ } batchData.Batch = batch rollupData.batches = append(rollupData.batches, *batchData) diff --git a/synchronizer/synchronizer_test.go b/synchronizer/synchronizer_test.go index 1c71fc9..8ff1a0b 100644 --- a/synchronizer/synchronizer_test.go +++ b/synchronizer/synchronizer_test.go @@ -2,6 +2,7 @@ package synchronizer import ( "context" + "encoding/json" "fmt" "io/ioutil" "math/big" @@ -21,6 +22,9 @@ import ( "github.com/stretchr/testify/require" ) +var tokenConsts = map[common.TokenID]eth.ERC20Consts{} +var forceExits = map[int64][]common.ExitInfo{} // ForgeL1TxsNum -> []exit + type timer struct { time int64 } @@ -31,6 +35,167 @@ func (t *timer) Time() int64 { return currentTime } +// Check Sync output and HistoryDB state against expected values generated by +// til +func checkSyncBlock(t *testing.T, s *Synchronizer, blockNum int, block, syncBlock *common.BlockData) { + // Check Blocks + dbBlocks, err := s.historyDB.GetAllBlocks() + require.Nil(t, err) + assert.Equal(t, blockNum, len(dbBlocks)) + assert.Equal(t, int64(blockNum), dbBlocks[blockNum-1].EthBlockNum) + assert.NotEqual(t, dbBlocks[blockNum-1].Hash, dbBlocks[blockNum-2].Hash) + assert.Greater(t, dbBlocks[blockNum-1].Timestamp.Unix(), dbBlocks[blockNum-2].Timestamp.Unix()) + + // Check Tokens + assert.Equal(t, len(block.AddedTokens), len(syncBlock.AddedTokens)) + dbTokens, err := s.historyDB.GetAllTokens() + require.Nil(t, err) + for i, token := range block.AddedTokens { + dbToken := dbTokens[i] + syncToken := syncBlock.AddedTokens[i] + + assert.Equal(t, block.Block.EthBlockNum, syncToken.EthBlockNum) + assert.Equal(t, token.TokenID, syncToken.TokenID) + assert.Equal(t, token.EthAddr, syncToken.EthAddr) + tokenConst := tokenConsts[token.TokenID] + assert.Equal(t, tokenConst.Name, syncToken.Name) + assert.Equal(t, tokenConst.Symbol, syncToken.Symbol) + assert.Equal(t, tokenConst.Decimals, syncToken.Decimals) + + var tokenCpy historydb.TokenWithUSD + //nolint:gosec + require.Nil(t, copier.Copy(&tokenCpy, &token)) // copy common.Token to historydb.TokenWithUSD + require.Nil(t, copier.Copy(&tokenCpy, &tokenConst)) // copy common.Token to historydb.TokenWithUSD + tokenCpy.ItemID = dbToken.ItemID // we don't care about ItemID + assert.Equal(t, tokenCpy, dbToken) + } + + // Check L1UserTxs + assert.Equal(t, len(block.L1UserTxs), len(syncBlock.L1UserTxs)) + dbL1UserTxs, err := s.historyDB.GetAllL1UserTxs() + require.Nil(t, err) + // Ignore BatchNum in syncBlock.L1UserTxs because this value is set by the HistoryDB + for i := range syncBlock.L1UserTxs { + syncBlock.L1UserTxs[i].BatchNum = block.L1UserTxs[i].BatchNum + } + assert.Equal(t, block.L1UserTxs, syncBlock.L1UserTxs) + for _, tx := range block.L1UserTxs { + var dbTx *common.L1Tx + // Find tx in DB output + for _, _dbTx := range dbL1UserTxs { + if *tx.ToForgeL1TxsNum == *_dbTx.ToForgeL1TxsNum && + tx.Position == _dbTx.Position { + dbTx = new(common.L1Tx) + *dbTx = _dbTx + break + } + } + assert.Equal(t, &tx, dbTx) //nolint:gosec + } + + // Check Batches + assert.Equal(t, len(block.Batches), len(syncBlock.Batches)) + dbBatches, err := s.historyDB.GetAllBatches() + require.Nil(t, err) + + dbL1CoordinatorTxs, err := s.historyDB.GetAllL1CoordinatorTxs() + require.Nil(t, err) + // fmt.Printf("DBG dbL1CoordinatorTxs: %+v\n", dbL1CoordinatorTxs) + dbL2Txs, err := s.historyDB.GetAllL2Txs() + require.Nil(t, err) + // fmt.Printf("DBG dbL2Txs: %+v\n", dbL2Txs) + dbExits, err := s.historyDB.GetAllExits() + require.Nil(t, err) + // dbL1CoordinatorTxs := []common.L1Tx{} + for i, batch := range block.Batches { + var dbBatch *common.Batch + // Find batch in DB output + for _, _dbBatch := range dbBatches { + if batch.Batch.BatchNum == _dbBatch.BatchNum { + dbBatch = new(common.Batch) + *dbBatch = _dbBatch + break + } + } + syncBatch := syncBlock.Batches[i] + + // We don't care about TotalFeesUSD. Use the syncBatch that + // has a TotalFeesUSD inserted by the HistoryDB + batch.Batch.TotalFeesUSD = syncBatch.Batch.TotalFeesUSD + batch.CreatedAccounts = syncBatch.CreatedAccounts // til doesn't output CreatedAccounts + + // Test field by field to facilitate debugging of errors + assert.Equal(t, batch.L1CoordinatorTxs, syncBatch.L1CoordinatorTxs) + assert.Equal(t, batch.L2Txs, syncBatch.L2Txs) + // In exit tree, we only check AccountIdx and Balance, because + // it's what we have precomputed before. + for j := range batch.ExitTree { + exit := &batch.ExitTree[j] + assert.Equal(t, exit.AccountIdx, syncBatch.ExitTree[j].AccountIdx) + assert.Equal(t, exit.Balance, syncBatch.ExitTree[j].Balance) + *exit = syncBatch.ExitTree[j] + } + assert.Equal(t, batch.Batch, syncBatch.Batch) + assert.Equal(t, batch, syncBatch) + assert.Equal(t, &batch.Batch, dbBatch) //nolint:gosec + + // Check L1CoordinatorTxs from DB + for _, tx := range batch.L1CoordinatorTxs { + var dbTx *common.L1Tx + // Find tx in DB output + for _, _dbTx := range dbL1CoordinatorTxs { + if *tx.BatchNum == *_dbTx.BatchNum && + tx.Position == _dbTx.Position { + dbTx = new(common.L1Tx) + *dbTx = _dbTx + break + } + } + assert.Equal(t, &tx, dbTx) //nolint:gosec + } + + // Check L2Txs from DB + for _, tx := range batch.L2Txs { + var dbTx *common.L2Tx + // Find tx in DB output + for _, _dbTx := range dbL2Txs { + if tx.BatchNum == _dbTx.BatchNum && + tx.Position == _dbTx.Position { + dbTx = new(common.L2Tx) + *dbTx = _dbTx + break + } + } + assert.Equal(t, &tx, dbTx) //nolint:gosec + } + + // Check Exits from DB + for _, exit := range batch.ExitTree { + var dbExit *common.ExitInfo + // Find exit in DB output + for _, _dbExit := range dbExits { + if exit.BatchNum == _dbExit.BatchNum && + exit.AccountIdx == _dbExit.AccountIdx { + dbExit = new(common.ExitInfo) + *dbExit = _dbExit + break + } + } + // Compare MerkleProof in JSON because unmarshaled 0 + // big.Int leaves the internal big.Int array at nil, + // and gives trouble when comparing big.Int with + // internal big.Int array != nil but empty. + mtp, err := json.Marshal(exit.MerkleProof) + require.Nil(t, err) + dbMtp, err := json.Marshal(dbExit.MerkleProof) + require.Nil(t, err) + assert.Equal(t, mtp, dbMtp) + dbExit.MerkleProof = exit.MerkleProof + assert.Equal(t, &exit, dbExit) //nolint:gosec + } + } +} + func TestSync(t *testing.T) { // // Setup @@ -89,6 +254,10 @@ func TestSync(t *testing.T) { // Generate blockchain and smart contract data, and fill the test smart contracts // + // TODO: Test CreateAccountDepositTransfer + // TODO: Test ForceTransfer + // TODO: Test ForceExit + // Generate blockchain data with til set1 := ` Type: Blockchain @@ -97,28 +266,44 @@ func TestSync(t *testing.T) { AddToken(2) AddToken(3) - CreateAccountDeposit(1) A: 20 // Idx=256+1 - CreateAccountDeposit(2) A: 20 // Idx=256+2 - CreateAccountDeposit(1) B: 5 // Idx=256+3 - CreateAccountDeposit(1) C: 5 // Idx=256+4 - CreateAccountDeposit(1) D: 5 // Idx=256+5 + CreateAccountDeposit(1) C: 20 // Idx=256+2=258 + CreateAccountDeposit(2) A: 20 // Idx=256+3=259 + CreateAccountDeposit(1) D: 5 // Idx=256+4=260 + CreateAccountDeposit(2) B: 5 // Idx=256+5=261 + CreateAccountDeposit(2) C: 5 // Idx=256+6=262 - CreateAccountCoordinator(2) B // Idx=256+0 + CreateAccountCoordinator(1) A // Idx=256+0=256 + CreateAccountCoordinator(1) B // Idx=256+1=257 > batchL1 // forge L1UserTxs{nil}, freeze defined L1UserTxs > batchL1 // forge defined L1UserTxs, freeze L1UserTxs{nil} - > block + > block // blockNum=2 + + Transfer(1) C-A: 10 (10) + Exit(1) D: 3 (5) + + > batch + > block // blockNum=3 ` tc := til.NewContext(eth.RollupConstMaxL1UserTx) blocks, err := tc.GenerateBlocks(set1) require.Nil(t, err) - require.Equal(t, 1, len(blocks)) - require.Equal(t, 3, len(blocks[0].AddedTokens)) - require.Equal(t, 5, len(blocks[0].L1UserTxs)) - require.Equal(t, 2, len(blocks[0].Batches)) + // Sanity check + require.Equal(t, 2, len(blocks)) + // blocks 0 (blockNum=2) + i := 0 + require.Equal(t, 2, int(blocks[i].Block.EthBlockNum)) + require.Equal(t, 3, len(blocks[i].AddedTokens)) + require.Equal(t, 5, len(blocks[i].L1UserTxs)) + require.Equal(t, 2, len(blocks[i].Batches)) + require.Equal(t, 2, len(blocks[i].Batches[0].L1CoordinatorTxs)) + // blocks 1 (blockNum=3) + i = 1 + require.Equal(t, 3, int(blocks[i].Block.EthBlockNum)) + require.Equal(t, 1, len(blocks[i].Batches)) + require.Equal(t, 2, len(blocks[i].Batches[0].L2Txs)) - tokenConsts := map[common.TokenID]eth.ERC20Consts{} // Generate extra required data for _, block := range blocks { for _, token := range block.AddedTokens { @@ -167,21 +352,25 @@ func TestSync(t *testing.T) { client.CtlMineBlock() } - // - // Sync to synchronize the current state from the test smart contracts - // - - syncBlock, discards, err = s.Sync2(ctx, nil) - require.Nil(t, err) - require.Nil(t, discards) - require.NotNil(t, syncBlock) - assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum) - // Fill extra fields not generated by til in til block openToForge := int64(0) toForgeL1TxsNum := int64(0) + l1UserTxsLen := map[int64]int{} // ForgeL1TxsNum -> len(L1UserTxs) for i := range blocks { block := &blocks[i] + // Count number of L1UserTxs in each queue, to figure out later + // position of L1CoordinatorTxs and L2Txs + for j := range block.L1UserTxs { + tx := &block.L1UserTxs[j] + l1UserTxsLen[*tx.ToForgeL1TxsNum]++ + if tx.Type == common.TxTypeForceExit { + forceExits[*tx.ToForgeL1TxsNum] = append(forceExits[*tx.ToForgeL1TxsNum], + common.ExitInfo{ + AccountIdx: tx.FromIdx, + Balance: tx.Amount, + }) + } + } for j := range block.Batches { batch := &block.Batches[j] if batch.L1Batch { @@ -208,94 +397,90 @@ func TestSync(t *testing.T) { } batchNum := batch.Batch.BatchNum - for j := range batch.L1CoordinatorTxs { - tx := &batch.L1CoordinatorTxs[j] + for k := range batch.L1CoordinatorTxs { + tx := &batch.L1CoordinatorTxs[k] tx.BatchNum = &batchNum tx.EthBlockNum = batch.Batch.EthBlockNum + } + } + } + + // Fill expected positions in L1CoordinatorTxs and L2Txs + for i := range blocks { + block := &blocks[i] + for j := range block.Batches { + batch := &block.Batches[j] + position := 0 + if batch.L1Batch { + position = l1UserTxsLen[*batch.Batch.ForgeL1TxsNum] + } + for k := range batch.L1CoordinatorTxs { + tx := &batch.L1CoordinatorTxs[k] + tx.Position = position + position++ nTx, err := common.NewL1Tx(tx) require.Nil(t, err) *tx = *nTx } + for k := range batch.L2Txs { + tx := &batch.L2Txs[k] + tx.Position = position + position++ + nTx, err := common.NewL2Tx(tx) + require.Nil(t, err) + *tx = *nTx + } } } - block := blocks[0] + // Fill ExitTree (only AccountIdx and Balance) + for i := range blocks { + block := &blocks[i] + for j := range block.Batches { + batch := &block.Batches[j] + if batch.L1Batch { + for forgeL1TxsNum, exits := range forceExits { + if forgeL1TxsNum == *batch.Batch.ForgeL1TxsNum { + batch.ExitTree = append(batch.ExitTree, exits...) + } + } + } + for k := range batch.L2Txs { + tx := &batch.L2Txs[k] + if tx.Type == common.TxTypeExit { + batch.ExitTree = append(batch.ExitTree, common.ExitInfo{ + AccountIdx: tx.FromIdx, + Balance: tx.Amount, + }) + } + } + } + } // - // Check Sync output and HistoryDB state against expected values - // generated by til + // Sync to synchronize the current state from the test smart contracts, + // and check the outcome // - // Check Blocks - dbBlocks, err = s.historyDB.GetAllBlocks() - require.Nil(t, err) - assert.Equal(t, 2, len(dbBlocks)) - assert.Equal(t, int64(2), dbBlocks[1].EthBlockNum) - assert.NotEqual(t, dbBlocks[1].Hash, dbBlocks[0].Hash) - assert.Greater(t, dbBlocks[1].Timestamp.Unix(), dbBlocks[0].Timestamp.Unix()) + // Block 2 - // Check Tokens - assert.Equal(t, len(block.AddedTokens), len(syncBlock.AddedTokens)) - dbTokens, err := s.historyDB.GetAllTokens() + syncBlock, discards, err = s.Sync2(ctx, nil) require.Nil(t, err) - assert.Equal(t, len(block.AddedTokens), len(dbTokens)) - for i, token := range block.AddedTokens { - dbToken := dbTokens[i] - syncToken := syncBlock.AddedTokens[i] - - assert.Equal(t, block.Block.EthBlockNum, syncToken.EthBlockNum) - assert.Equal(t, token.TokenID, syncToken.TokenID) - assert.Equal(t, token.EthAddr, syncToken.EthAddr) - tokenConst := tokenConsts[token.TokenID] - assert.Equal(t, tokenConst.Name, syncToken.Name) - assert.Equal(t, tokenConst.Symbol, syncToken.Symbol) - assert.Equal(t, tokenConst.Decimals, syncToken.Decimals) + require.Nil(t, discards) + require.NotNil(t, syncBlock) + assert.Equal(t, int64(2), syncBlock.Block.EthBlockNum) - var tokenCpy historydb.TokenWithUSD - //nolint:gosec - require.Nil(t, copier.Copy(&tokenCpy, &token)) // copy common.Token to historydb.TokenWithUSD - require.Nil(t, copier.Copy(&tokenCpy, &tokenConst)) // copy common.Token to historydb.TokenWithUSD - tokenCpy.ItemID = dbToken.ItemID // we don't care about ItemID - assert.Equal(t, tokenCpy, dbToken) - } + checkSyncBlock(t, s, 2, &blocks[0], syncBlock) - // Check L1UserTxs - assert.Equal(t, len(block.L1UserTxs), len(syncBlock.L1UserTxs)) - dbL1UserTxs, err := s.historyDB.GetAllL1UserTxs() - require.Nil(t, err) - assert.Equal(t, len(block.L1UserTxs), len(dbL1UserTxs)) - // Ignore BatchNum in syncBlock.L1UserTxs because this value is set by the HistoryDB - for i := range syncBlock.L1UserTxs { - syncBlock.L1UserTxs[i].BatchNum = block.L1UserTxs[i].BatchNum - } - assert.Equal(t, block.L1UserTxs, syncBlock.L1UserTxs) - assert.Equal(t, block.L1UserTxs, dbL1UserTxs) + // Block 3 - // Check Batches - assert.Equal(t, len(block.Batches), len(syncBlock.Batches)) - dbBatches, err := s.historyDB.GetAllBatches() + syncBlock, discards, err = s.Sync2(ctx, nil) require.Nil(t, err) - assert.Equal(t, len(block.Batches), len(dbBatches)) - - for i, batch := range block.Batches { - batchNum := batch.Batch.BatchNum - dbBatch := dbBatches[i] - syncBatch := syncBlock.Batches[i] - - // We don't care about TotalFeesUSD. Use the syncBatch that - // has a TotalFeesUSD inserted by the HistoryDB - batch.Batch.TotalFeesUSD = syncBatch.Batch.TotalFeesUSD - batch.CreatedAccounts = syncBatch.CreatedAccounts // til doesn't output CreatedAccounts - - // fmt.Printf("DBG Batch %d %+v\n", i, batch) - // fmt.Printf("DBG Batch Sync %d %+v\n", i, syncBatch) - // assert.Equal(t, batch.L1CoordinatorTxs, syncBatch.L1CoordinatorTxs) - fmt.Printf("DBG BatchNum: %d, LastIdx: %d\n", batchNum, batch.Batch.LastIdx) - assert.Equal(t, batch, syncBatch) - assert.Equal(t, batch.Batch, dbBatch) - } + require.Nil(t, discards) + require.NotNil(t, syncBlock) + assert.Equal(t, int64(3), syncBlock.Block.EthBlockNum) - // Check L1UserTxs in DB + checkSyncBlock(t, s, 3, &blocks[1], syncBlock) // TODO: Reorg will be properly tested once we have the mock ethClient implemented /* diff --git a/test/til/txs.go b/test/til/txs.go index c6c1277..d404421 100644 --- a/test/til/txs.go +++ b/test/til/txs.go @@ -25,6 +25,15 @@ func newBatchData(batchNum int) common.BatchData { } } +func newBlock(blockNum int64) common.BlockData { + return common.BlockData{ + Block: common.Block{ + EthBlockNum: blockNum, + }, + L1UserTxs: []common.L1Tx{}, + } +} + // Context contains the data of the test type Context struct { Instructions []instruction @@ -62,6 +71,7 @@ func NewContext(rollupConstMaxL1UserTx int) *Context { idx: common.UserThreshold, // We use some placeholder values for StateRoot and ExitTree // because these values will never be nil + currBlock: newBlock(2), //nolint:gomnd currBatch: newBatchData(currBatchNum), currBatchNum: currBatchNum, // start with 2 queues, one for toForge, and the other for openToForge @@ -206,9 +216,10 @@ func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) { return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L2Tx{ - Amount: big.NewInt(int64(inst.amount)), - Fee: common.FeeSelector(inst.fee), - Type: common.TxTypeTransfer, + Amount: big.NewInt(int64(inst.amount)), + Fee: common.FeeSelector(inst.fee), + Type: common.TxTypeTransfer, + EthBlockNum: tc.blockNum, } tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost testTx := L2Tx{ @@ -245,9 +256,10 @@ func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) { return nil, fmt.Errorf("Line %d: %s", inst.lineNum, err.Error()) } tx := common.L2Tx{ - ToIdx: common.Idx(1), // as is an Exit - Amount: big.NewInt(int64(inst.amount)), - Type: common.TxTypeExit, + ToIdx: common.Idx(1), // as is an Exit + Amount: big.NewInt(int64(inst.amount)), + Type: common.TxTypeExit, + EthBlockNum: tc.blockNum, } tx.BatchNum = common.BatchNum(tc.currBatchNum) // when converted to PoolL2Tx BatchNum parameter is lost testTx := L2Tx{ @@ -308,12 +320,9 @@ func (tc *Context) GenerateBlocks(set string) ([]common.BlockData, error) { tc.queues = append(tc.queues, newQueue) } case typeNewBlock: - tc.currBlock.Block = common.Block{ - EthBlockNum: tc.blockNum, - } blocks = append(blocks, tc.currBlock) tc.blockNum++ - tc.currBlock = common.BlockData{} + tc.currBlock = newBlock(tc.blockNum) case typeAddToken: newToken := common.Token{ EthAddr: ethCommon.BigToAddress(big.NewInt(int64(inst.tokenID * 100))), //nolint:gomnd