diff --git a/txselector/txselector.go b/txselector/txselector.go index 60cc7e5..46338b7 100644 --- a/txselector/txselector.go +++ b/txselector/txselector.go @@ -164,30 +164,33 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, // 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, } } - 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) + } + + // 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) + } - // 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)" + 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, // - 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, // 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, } // 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, // 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, // 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 @@ func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config, 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 @@ func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx, tokenID common. 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 @@ func (txsel *TxSelector) getL2Profitable(l2Txs []common.PoolL2Tx, max uint32) ([ 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 } diff --git a/txselector/txselector_test.go b/txselector/txselector_test.go index 35a4156..78e7834 100644 --- a/txselector/txselector_test.go +++ b/txselector/txselector_test.go @@ -26,12 +26,14 @@ import ( ) 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, 1000.0, 24*time.Hour, nil) + historyDB := historydb.NewHistoryDB(db, db, nil) + dir, err := ioutil.TempDir("", "tmpdb") require.NoError(t, err) defer assert.NoError(t, os.RemoveAll(dir)) @@ -65,7 +67,7 @@ func initTest(t *testing.T, chainID uint16, hermezContractAddr ethCommon.Address 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 @@ func TestGetL2TxSelectionMinimumFlow0(t *testing.T) { 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 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { 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 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { 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 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { 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 @@ func TestPoolL2TxsWithoutEnoughBalance(t *testing.T) { 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 @@ func TestTransferToBjj(t *testing.T) { 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 @@ func TestTransferManyFromSameAccount(t *testing.T) { 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 @@ func TestPoolL2TxInvalidNonces(t *testing.T) { 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 @@ func TestProcessL2Selection(t *testing.T) { 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 @@ func TestProcessL2Selection(t *testing.T) { 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)) +}