Update TxSel selection to prioritize the forgable Previous to this commit, there were cases where being len(nonForgableL2Txs)>maxL2Txs and nonForgableL2Txs have bigger fee than forgableL2Txs, the forgableTxs where never forged, neither the nonForgableTxs. Now, the TxSelector first forges the forgableTxs (which are forgable for the initial state of the accounts (balances & nonces), and then the nonForgableL2Txs, which may be unblocked once the forgable ones have been processed.
@@ -164,30 +164,33 @@ // current version is implemented in order to have a functional
// implementation that can be used ASAP. // Steps of this method: - // - getPendingTxs - // - ProcessL1Txs - // - getProfitable (sort by fee & nonce) - // - loop over l2Txs - // - Fill tx.TokenID tx.Nonce - // - Check enough Balance on sender - // - Check Nonce - // - Create CoordAccount L1CoordTx for TokenID if needed - // - & ProcessL1Tx of L1CoordTx - // - Check validity of receiver Account for ToEthAddr / ToBJJ - // - Create UserAccount L1CoordTx if needed (and possible) - // - If everything is fine, store l2Tx to validTxs & update NoncesMap + // - ProcessL1Txs (User txs) + // - getPendingTxs (forgable directly with current state & not forgable + // yet) + // - split between l2TxsForgable & l2TxsNonForgable, where: + // - l2TxsForgable are the txs that are directly forgable with the + // current state + // - l2TxsNonForgable are the txs that are not directly forgable + // with the current state, but that may be forgable once the + // l2TxsForgable ones are processed + // - for l2TxsForgable, and if needed, for l2TxsNonForgable: + // - sort by Fee & Nonce + // - loop over l2Txs (txsel.processL2Txs) + // - Fill tx.TokenID tx.Nonce + // - Check enough Balance on sender + // - Check Nonce + // - Create CoordAccount L1CoordTx for TokenID if needed + // - & ProcessL1Tx of L1CoordTx + // - Check validity of receiver Account for ToEthAddr / ToBJJ + // - Create UserAccount L1CoordTx if needed (and possible) + // - If everything is fine, store l2Tx to validTxs & update NoncesMap // - Prepare coordIdxsMap & AccumulatedFees // - Distribute AccumulatedFees to CoordIdxs // - MakeCheckpoint - // get pending l2-tx from tx-pool - l2TxsRaw, err := txsel.l2db.GetPendingTxs() - if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - txselStateDB := txsel.localAccountsDB.StateDB tp := txprocessor.NewTxProcessor(txselStateDB, selectionConfig) + tp.AccumulatedFees = make(map[common.Idx]*big.Int) // Process L1UserTxs for i := 0; i < len(l1UserTxs); i++ {@@ -198,21 +201,139 @@ return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
} } - var l1CoordinatorTxs []common.L1Tx - positionL1 := len(l1UserTxs) + l2TxsFromDB, err := txsel.l2db.GetPendingTxs() + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + l2TxsForgable, l2TxsNonForgable := splitL2ForgableAndNonForgable(tp, l2TxsFromDB) + + // in case that length of l2TxsForgable is 0, no need to continue, there + // is no L2Txs to forge at all + if len(l2TxsForgable) == 0 { + var discardedL2Txs []common.PoolL2Tx + for i := 0; i < len(l2TxsNonForgable); i++ { + l2TxsNonForgable[i].Info = + "Tx not selected due impossibility to be forged with the current state" + discardedL2Txs = append(discardedL2Txs, l2TxsNonForgable[i]) + } + err = tp.StateDB().MakeCheckpoint() + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + + metricSelectedL1UserTxs.Set(float64(len(l1UserTxs))) + metricSelectedL1CoordinatorTxs.Set(0) + metricSelectedL2Txs.Set(0) + metricDiscardedL2Txs.Set(float64(len(discardedL2Txs))) + return nil, nil, l1UserTxs, nil, nil, discardedL2Txs, nil + } + var accAuths [][]byte + var l1CoordinatorTxs []common.L1Tx + var validTxs, discardedL2Txs []common.PoolL2Tx + l2TxsForgable = sortL2Txs(l2TxsForgable) + accAuths, l1CoordinatorTxs, validTxs, discardedL2Txs, err = + txsel.processL2Txs(tp, selectionConfig, len(l1UserTxs), + l2TxsForgable, validTxs, discardedL2Txs) + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } - // Sort l2TxsRaw (cropping at MaxTx at this point). - // discardedL2Txs contains an array of the L2Txs that have not been - // selected in this Batch. - l2Txs, discardedL2Txs := txsel.getL2Profitable(l2TxsRaw, selectionConfig.MaxTx-uint32(len(l1UserTxs))) - for i := range discardedL2Txs { - discardedL2Txs[i].Info = - "Tx not selected due to low absolute fee (does not fit inside the profitable set)" + // if there is space for more txs get also the NonForgable txs, that may + // be unblocked once the Forgable ones are processed + if len(validTxs) < int(selectionConfig.MaxTx)-(len(l1UserTxs)+len(l1CoordinatorTxs)) { + l2TxsNonForgable = sortL2Txs(l2TxsNonForgable) + var accAuths2 [][]byte + var l1CoordinatorTxs2 []common.L1Tx + accAuths2, l1CoordinatorTxs2, validTxs, discardedL2Txs, err = + txsel.processL2Txs(tp, selectionConfig, + len(l1UserTxs)+len(l1CoordinatorTxs), l2TxsNonForgable, + validTxs, discardedL2Txs) + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + + accAuths = append(accAuths, accAuths2...) + l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTxs2...) + } else { + // if there is no space for NonForgable txs, put them at the + // discardedL2Txs array + for i := 0; i < len(l2TxsNonForgable); i++ { + l2TxsNonForgable[i].Info = + "Tx not selected due not available slots for L2Txs" + discardedL2Txs = append(discardedL2Txs, l2TxsNonForgable[i]) + } } - var validTxs []common.PoolL2Tx - tp.AccumulatedFees = make(map[common.Idx]*big.Int) + // get CoordIdxsMap for the TokenIDs + coordIdxsMap := make(map[common.TokenID]common.Idx) + for i := 0; i < len(validTxs); i++ { + // get TokenID from tx.Sender + accSender, err := tp.StateDB().GetAccount(validTxs[i].FromIdx) + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + tokenID := accSender.TokenID + + coordIdx, err := txsel.getCoordIdx(tokenID) + if err != nil { + // if err is db.ErrNotFound, should not happen, as all + // the validTxs.TokenID should have a CoordinatorIdx + // created in the DB at this point + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + coordIdxsMap[tokenID] = coordIdx + } + + var coordIdxs []common.Idx + for _, idx := range coordIdxsMap { + coordIdxs = append(coordIdxs, idx) + } + // sort CoordIdxs + sort.SliceStable(coordIdxs, func(i, j int) bool { + return coordIdxs[i] < coordIdxs[j] + }) + + // distribute the AccumulatedFees from the processed L2Txs into the + // Coordinator Idxs + for idx, accumulatedFee := range tp.AccumulatedFees { + cmp := accumulatedFee.Cmp(big.NewInt(0)) + if cmp == 1 { // accumulatedFee>0 + // send the fee to the Idx of the Coordinator for the TokenID + accCoord, err := txsel.localAccountsDB.GetAccount(idx) + if err != nil { + log.Errorw("Can not distribute accumulated fees to coordinator "+ + "account: No coord Idx to receive fee", "idx", idx) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + accCoord.Balance = new(big.Int).Add(accCoord.Balance, accumulatedFee) + _, err = txsel.localAccountsDB.UpdateAccount(idx, accCoord) + if err != nil { + log.Error(err) + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + } + } + + err = tp.StateDB().MakeCheckpoint() + if err != nil { + return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + } + + metricSelectedL1UserTxs.Set(float64(len(l1UserTxs))) + metricSelectedL1CoordinatorTxs.Set(float64(len(l1CoordinatorTxs))) + metricSelectedL2Txs.Set(float64(len(validTxs))) + metricDiscardedL2Txs.Set(float64(len(discardedL2Txs))) + + return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, validTxs, discardedL2Txs, nil +} + +func (txsel *TxSelector) processL2Txs(tp *txprocessor.TxProcessor, + selectionConfig txprocessor.Config, nL1Txs int, l2Txs, validTxs, discardedL2Txs []common.PoolL2Tx) ( + [][]byte, []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) { + var l1CoordinatorTxs []common.L1Tx + positionL1 := nL1Txs + var accAuths [][]byte // Iterate over l2Txs // - check Nonces // - check enough Balance for the Amount+Fee@@ -221,20 +342,22 @@ // - keep used accAuths
// - put the valid txs into validTxs array for i := 0; i < len(l2Txs); i++ { // Check if there is space for more L2Txs in the selection - maxL2Txs := int(selectionConfig.MaxTx) - - len(l1UserTxs) - len(l1CoordinatorTxs) + maxL2Txs := int(selectionConfig.MaxTx) - nL1Txs - len(l1CoordinatorTxs) if len(validTxs) >= maxL2Txs { - // no more available slots for L2Txs - l2Txs[i].Info = - "Tx not selected due not available slots for L2Txs" - discardedL2Txs = append(discardedL2Txs, l2Txs[i]) - continue + // no more available slots for L2Txs, so mark this tx + // but also the rest of remaining txs as discarded + for j := i; j < len(l2Txs); j++ { + l2Txs[j].Info = + "Tx not selected due not available slots for L2Txs" + discardedL2Txs = append(discardedL2Txs, l2Txs[j]) + } + break } // get Nonce & TokenID from the Account by l2Tx.FromIdx accSender, err := tp.StateDB().GetAccount(l2Txs[i].FromIdx) if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, tracerr.Wrap(err) } l2Txs[i].TokenID = accSender.TokenID@@ -272,13 +395,13 @@ newL1CoordTx, positionL1, err =
txsel.coordAccountForTokenID(l1CoordinatorTxs, accSender.TokenID, positionL1) if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, tracerr.Wrap(err) } if newL1CoordTx != nil { // if there is no space for the L1CoordinatorTx as MaxL1Tx, or no space // for L1CoordinatorTx + L2Tx as MaxTx, discard the L2Tx - if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-len(l1UserTxs) || - len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-len(l1UserTxs) { + if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-nL1Txs || + len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-nL1Txs { // discard L2Tx, and update Info parameter of // the tx, and add it to the discardedTxs array l2Txs[i].Info = "Tx not selected because the L2Tx depends on a " +@@ -294,7 +417,7 @@
// process the L1CoordTx _, _, _, _, err := tp.ProcessL1Tx(nil, newL1CoordTx) if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, tracerr.Wrap(err) } }@@ -309,7 +432,7 @@ // Exit type and is used.
if l2Txs[i].ToIdx == 0 { // ToEthAddr/ToBJJ case validL2Tx, l1CoordinatorTx, accAuth, err := txsel.processTxToEthAddrBJJ(validTxs, selectionConfig, - len(l1UserTxs), l1CoordinatorTxs, positionL1, l2Txs[i]) + nL1Txs, l1CoordinatorTxs, positionL1, l2Txs[i]) if err != nil { log.Debugw("txsel.processTxToEthAddrBJJ", "err", err) // Discard L2Tx, and update Info parameter of@@ -321,8 +444,8 @@ continue
} // if there is no space for the L1CoordinatorTx as MaxL1Tx, or no space // for L1CoordinatorTx + L2Tx as MaxTx, discard the L2Tx - if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-len(l1UserTxs) || - len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-len(l1UserTxs) { + if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-nL1Txs || + len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-nL1Txs { // discard L2Tx, and update Info parameter of // the tx, and add it to the discardedTxs array l2Txs[i].Info = "Tx not selected because the L2Tx depends on a " +@@ -351,7 +474,7 @@
// process the L1CoordTx _, _, _, _, err := tp.ProcessL1Tx(nil, l1CoordinatorTx) if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) + return nil, nil, nil, nil, tracerr.Wrap(err) } } if validL2Tx == nil {@@ -413,7 +536,7 @@ if err != nil {
// if err is db.ErrNotFound, should not happen, as all // the validTxs.TokenID should have a CoordinatorIdx // created in the DB at this point - return nil, nil, nil, nil, nil, nil, + return nil, nil, nil, nil, tracerr.Wrap(fmt.Errorf("Could not get CoordIdx for TokenID=%d, "+ "due: %s", tokenID, err)) }@@ -439,68 +562,7 @@
validTxs = append(validTxs, l2Txs[i]) } // after this loop, no checks to discard txs should be done - // get CoordIdxsMap for the TokenIDs - coordIdxsMap := make(map[common.TokenID]common.Idx) - for i := 0; i < len(validTxs); i++ { - // get TokenID from tx.Sender - accSender, err := tp.StateDB().GetAccount(validTxs[i].FromIdx) - if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - tokenID := accSender.TokenID - - coordIdx, err := txsel.getCoordIdx(tokenID) - if err != nil { - // if err is db.ErrNotFound, should not happen, as all - // the validTxs.TokenID should have a CoordinatorIdx - // created in the DB at this point - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - coordIdxsMap[tokenID] = coordIdx - } - - var coordIdxs []common.Idx - for _, idx := range coordIdxsMap { - coordIdxs = append(coordIdxs, idx) - } - // sort CoordIdxs - sort.SliceStable(coordIdxs, func(i, j int) bool { - return coordIdxs[i] < coordIdxs[j] - }) - - // distribute the AccumulatedFees from the processed L2Txs into the - // Coordinator Idxs - for idx, accumulatedFee := range tp.AccumulatedFees { - cmp := accumulatedFee.Cmp(big.NewInt(0)) - if cmp == 1 { // accumulatedFee>0 - // send the fee to the Idx of the Coordinator for the TokenID - accCoord, err := txsel.localAccountsDB.GetAccount(idx) - if err != nil { - log.Errorw("Can not distribute accumulated fees to coordinator "+ - "account: No coord Idx to receive fee", "idx", idx) - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - accCoord.Balance = new(big.Int).Add(accCoord.Balance, accumulatedFee) - _, err = txsel.localAccountsDB.UpdateAccount(idx, accCoord) - if err != nil { - log.Error(err) - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - } - } - - err = tp.StateDB().MakeCheckpoint() - if err != nil { - return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err) - } - - metricSelectedL1CoordinatorTxs.Set(float64(len(l1CoordinatorTxs))) - metricSelectedL1UserTxs.Set(float64(len(l1UserTxs))) - metricSelectedL2Txs.Set(float64(len(validTxs))) - metricDiscardedL2Txs.Set(float64(len(discardedL2Txs))) - - // return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, validTxs, discardedL2Txs, nil - return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, validTxs, discardedL2Txs, nil + return accAuths, l1CoordinatorTxs, validTxs, discardedL2Txs, nil } // processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where@@ -636,26 +698,14 @@ }
return false } -// getL2Profitable returns the profitable selection of L2Txssorted by Nonce -func (txsel *TxSelector) getL2Profitable(l2Txs []common.PoolL2Tx, max uint32) ([]common.PoolL2Tx, - []common.PoolL2Tx) { - // First sort by nonce so that txs from the same account are sorted so - // that they could be applied in succession. - sort.Slice(l2Txs, func(i, j int) bool { - return l2Txs[i].Nonce < l2Txs[j].Nonce - }) +// sortL2Txs sorts the PoolL2Txs by AbsoluteFee and then by Nonce +func sortL2Txs(l2Txs []common.PoolL2Tx) []common.PoolL2Tx { // Sort by absolute fee with SliceStable, so that txs with same // AbsoluteFee are not rearranged and nonce order is kept in such case sort.SliceStable(l2Txs, func(i, j int) bool { return l2Txs[i].AbsoluteFee > l2Txs[j].AbsoluteFee }) - discardedL2Txs := []common.PoolL2Tx{} - if len(l2Txs) > int(max) { - discardedL2Txs = l2Txs[max:] - l2Txs = l2Txs[:max] - } - // sort l2Txs by Nonce. This can be done in many different ways, what // is needed is to output the l2Txs where the Nonce of l2Txs for each // Account is sorted, but the l2Txs can not be grouped by sender Account@@ -665,5 +715,29 @@ sort.Slice(l2Txs, func(i, j int) bool {
return l2Txs[i].Nonce < l2Txs[j].Nonce }) - return l2Txs, discardedL2Txs + return l2Txs +} + +func splitL2ForgableAndNonForgable(tp *txprocessor.TxProcessor, + l2Txs []common.PoolL2Tx) ([]common.PoolL2Tx, []common.PoolL2Tx) { + var l2TxsForgable, l2TxsNonForgable []common.PoolL2Tx + for i := 0; i < len(l2Txs); i++ { + accSender, err := tp.StateDB().GetAccount(l2Txs[i].FromIdx) + if err != nil { + l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i]) + continue + } + + if l2Txs[i].Nonce != accSender.Nonce { + l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i]) + continue + } + enoughBalance, _, _ := tp.CheckEnoughBalance(l2Txs[i]) + if !enoughBalance { + l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i]) + continue + } + l2TxsForgable = append(l2TxsForgable, l2Txs[i]) + } + return l2TxsForgable, l2TxsNonForgable }
@@ -26,11 +26,13 @@ "github.com/stretchr/testify/require"
) func initTest(t *testing.T, chainID uint16, hermezContractAddr ethCommon.Address, - coordUser *til.User) *TxSelector { + coordUser *til.User) (*TxSelector, *historydb.HistoryDB) { pass := os.Getenv("POSTGRES_PASS") db, err := dbUtils.InitSQLDB(5432, "localhost", "hermez", pass, "hermez") require.NoError(t, err) l2DB := l2db.NewL2DB(db, db, 10, 100, 0.0, 24*time.Hour, nil) + + historyDB := historydb.NewHistoryDB(db, db, nil) dir, err := ioutil.TempDir("", "tmpdb") require.NoError(t, err)@@ -65,7 +67,7 @@ require.NoError(t, err)
test.WipeDB(txsel.l2db.DB()) - return txsel + return txsel, historyDB } func addAccCreationAuth(t *testing.T, tc *til.Context, txsel *TxSelector, chainID uint16,@@ -157,7 +159,7 @@ blocks, err := tc.GenerateBlocks(txsets.SetBlockchainMinimumFlow0)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -417,7 +419,7 @@ blocks, err := tc.GenerateBlocks(set)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -470,11 +472,6 @@ // as the PoolL2Txs have not been really processed, restart nonces
tc.RestartNonces() // batch3 - // NOTE: this batch will result with 1 L2Tx, as the PoolExit tx is not - // possible, as the PoolTransferToEthAddr is not processed yet when - // checking availability of PoolExit. This, in a near-future iteration - // of the TxSelector will return the 2 transactions as valid and - // selected, as the TxSelector will handle this kind of combinations. batchPoolL2 = ` Type: PoolL2 PoolTransferToEthAddr(0) A-B: 50 (126)`@@ -488,11 +485,11 @@ txsel.GetL1L2TxSelection(tpc, l1UserTxs)
require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) - assert.Equal(t, 1, len(oL2Txs)) // see 'NOTE' at the beginning of 'batch3' of this test - assert.Equal(t, 2, len(discardedL2Txs)) + assert.Equal(t, 2, len(oL2Txs)) + assert.Equal(t, 1, len(discardedL2Txs)) assert.Equal(t, expectedTxID2, oL2Txs[0].TxID.String()) + assert.Equal(t, expectedTxID1, oL2Txs[1].TxID.String()) assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String()) - assert.Equal(t, expectedTxID1, discardedL2Txs[1].TxID.String()) assert.Equal(t, common.TxTypeTransferToEthAddr, oL2Txs[0].Type) err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.localAccountsDB.CurrentBatch())@@ -507,12 +504,8 @@ txsel.GetL1L2TxSelection(tpc, l1UserTxs)
require.NoError(t, err) assert.Equal(t, 0, len(oL1UserTxs)) assert.Equal(t, 0, len(oL1CoordTxs)) - assert.Equal(t, 1, len(oL2Txs)) + assert.Equal(t, 0, len(oL2Txs)) assert.Equal(t, 1, len(discardedL2Txs)) - // the Exit that was not accepted at the batch2 - assert.Equal(t, expectedTxID1, oL2Txs[0].TxID.String()) - assert.Equal(t, expectedTxID0, discardedL2Txs[0].TxID.String()) - assert.Equal(t, common.TxTypeExit, oL2Txs[0].Type) err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), txsel.localAccountsDB.CurrentBatch()) require.NoError(t, err)@@ -539,7 +532,7 @@ blocks, err := tc.GenerateBlocks(set)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -670,7 +663,7 @@ blocks, err := tc.GenerateBlocks(set)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -752,7 +745,7 @@ blocks, err := tc.GenerateBlocks(set)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -865,7 +858,7 @@ blocks, err := tc.GenerateBlocks(set)
assert.NoError(t, err) hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") - txsel := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + txsel, _ := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) // restart nonces of TilContext, as will be set by generating directly // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs@@ -921,3 +914,127 @@ err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs),
txsel.localAccountsDB.CurrentBatch()) require.NoError(t, err) } + +func TestValidTxsWithLowFeeAndInvalidTxsWithHighFee(t *testing.T) { + // This test recreates the case where there are + + set := ` + Type: Blockchain + + CreateAccountDeposit(0) Coord: 0 + CreateAccountDeposit(0) A: 100 + CreateAccountDeposit(0) B: 0 + + > batchL1 // Batch1: freeze L1User{3} + > batchL1 // Batch2: forge L1User{3} + > block + ` + + chainID := uint16(0) + tc := til.NewContext(chainID, common.RollupConstMaxL1UserTx) + tilCfgExtra := til.ConfigExtra{ + BootCoordAddr: ethCommon.HexToAddress("0xE39fEc6224708f0772D2A74fd3f9055A90E0A9f2"), + CoordUser: "Coord", + } + blocks, err := tc.GenerateBlocks(set) + require.NoError(t, err) + err = tc.FillBlocksExtra(blocks, &tilCfgExtra) + require.NoError(t, err) + err = tc.FillBlocksForgedL1UserTxs(blocks) + require.NoError(t, err) + + hermezContractAddr := ethCommon.HexToAddress("0xc344E203a046Da13b0B4467EB7B3629D0C99F6E6") + txsel, historyDB := initTest(t, chainID, hermezContractAddr, tc.Users["Coord"]) + + // Insert blocks into DB + for i := range blocks { + err = historyDB.AddBlockSCData(&blocks[i]) + assert.NoError(t, err) + } + + err = historyDB.UpdateTokenValue(common.EmptyAddr, 1000) + require.NoError(t, err) + + // restart nonces of TilContext, as will be set by generating directly + // the PoolL2Txs for each specific batch with tc.GeneratePoolL2Txs + tc.RestartNonces() + + tpc := txprocessor.Config{ + NLevels: 16, + MaxFeeTx: 5, + MaxTx: 5, + MaxL1Tx: 3, + ChainID: chainID, + } + // batch1 to freeze L1UserTxs + l1UserTxs := []common.L1Tx{} + _, _, _, _, _, _, err = txsel.GetL1L2TxSelection(tpc, l1UserTxs) + require.NoError(t, err) + + // batch 2 to crate the accounts (from L1UserTxs) + l1UserTxs = til.L1TxsToCommonL1Txs(tc.Queues[*blocks[0].Rollup.Batches[1].Batch.ForgeL1TxsNum]) + + // select L1 & L2 txs + _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err := + txsel.GetL1L2TxSelection(tpc, l1UserTxs) + require.NoError(t, err) + require.Equal(t, 3, len(oL1UserTxs)) + require.Equal(t, 0, len(oL1CoordTxs)) + require.Equal(t, 0, len(oL2Txs)) + require.Equal(t, 0, len(discardedL2Txs)) + require.Equal(t, 0, len(accAuths)) + + err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), + txsel.localAccountsDB.CurrentBatch()) + require.NoError(t, err) + + // batch 3. The A-B txs have lower fee, but are the only ones possible + // with the current Accounts Balances, as the B-A tx of amount 40 will + // not be included as will be processed first when there is not enough + // balance at B (processed first as the TxSelector sorts by Fee and then + // by Nonce). + batchPoolL2 := ` + Type: PoolL2 + PoolTransfer(0) B-A: 40 (130) // B-A txs are only possible once A-B txs are processed + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) B-A: 1 (126) + PoolTransfer(0) A-B: 20 (20) + PoolTransfer(0) A-B: 25 (150) + PoolTransfer(0) A-B: 20 (20) + ` + poolL2Txs, err := tc.GeneratePoolL2Txs(batchPoolL2) + require.NoError(t, err) + require.Equal(t, 11, len(poolL2Txs)) + // add the PoolL2Txs to the l2DB + addL2Txs(t, txsel, poolL2Txs) + l1UserTxs = []common.L1Tx{} + _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = + txsel.GetL1L2TxSelection(tpc, l1UserTxs) + require.NoError(t, err) + + require.Equal(t, 0, len(oL1UserTxs)) + require.Equal(t, 0, len(oL1CoordTxs)) + require.Equal(t, 3, len(oL2Txs)) // the 3 txs A-B + require.Equal(t, 8, len(discardedL2Txs)) // the 8 txs B-A + require.Equal(t, 0, len(accAuths)) + + err = txsel.l2db.StartForging(common.TxIDsFromPoolL2Txs(oL2Txs), + txsel.localAccountsDB.CurrentBatch()) + require.NoError(t, err) + + // batch 4. In this Batch, account B has enough balance to send the txs + _, accAuths, oL1UserTxs, oL1CoordTxs, oL2Txs, discardedL2Txs, err = + txsel.GetL1L2TxSelection(tpc, l1UserTxs) + require.NoError(t, err) + + require.Equal(t, 0, len(oL1UserTxs)) + require.Equal(t, 0, len(oL1CoordTxs)) + require.Equal(t, 5, len(oL2Txs)) + require.Equal(t, 3, len(discardedL2Txs)) + require.Equal(t, 0, len(accAuths)) +}