@ -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
}