hermez-node/txselector/txselector.go
txselector/txselector.goRaw
1package txselector
2
3// current: very simple version of TxSelector
4
5import (
6 "fmt"
7 "math/big"
8 "sort"
9
10 ethCommon "github.com/ethereum/go-ethereum/common"
11 "github.com/hermeznetwork/hermez-node/common"
12 "github.com/hermeznetwork/hermez-node/db/kvdb"
13 "github.com/hermeznetwork/hermez-node/db/l2db"
14 "github.com/hermeznetwork/hermez-node/db/statedb"
15 "github.com/hermeznetwork/hermez-node/log"
16 "github.com/hermeznetwork/hermez-node/metric"
17 "github.com/hermeznetwork/hermez-node/txprocessor"
18 "github.com/hermeznetwork/tracerr"
19 "github.com/iden3/go-iden3-crypto/babyjub"
20)
21
22// CoordAccount contains the data of the Coordinator account, that will be used
23// to create new transactions of CreateAccountDeposit type to add new TokenID
24// accounts for the Coordinator to receive the fees.
25type CoordAccount struct {
26 Addr ethCommon.Address
27 BJJ babyjub.PublicKeyComp
28 AccountCreationAuth []byte // signature in byte array format
29}
30
31// TxSelector implements all the functionalities to select the txs for the next
32// batch
33type TxSelector struct {
34 l2db *l2db.L2DB
35 localAccountsDB *statedb.LocalStateDB
36
37 coordAccount *CoordAccount
38}
39
40// NewTxSelector returns a *TxSelector
41func NewTxSelector(coordAccount *CoordAccount, dbpath string,
42 synchronizerStateDB *statedb.StateDB, l2 *l2db.L2DB) (*TxSelector, error) {
43 localAccountsDB, err := statedb.NewLocalStateDB(
44 statedb.Config{
45 Path: dbpath,
46 Keep: kvdb.DefaultKeep,
47 Type: statedb.TypeTxSelector,
48 NLevels: 0,
49 },
50 synchronizerStateDB) // without merkletree
51 if err != nil {
52 return nil, tracerr.Wrap(err)
53 }
54
55 return &TxSelector{
56 l2db: l2,
57 localAccountsDB: localAccountsDB,
58 coordAccount: coordAccount,
59 }, nil
60}
61
62// LocalAccountsDB returns the LocalStateDB of the TxSelector
63func (txsel *TxSelector) LocalAccountsDB() *statedb.LocalStateDB {
64 return txsel.localAccountsDB
65}
66
67// Reset tells the TxSelector to get it's internal AccountsDB
68// from the required `batchNum`
69func (txsel *TxSelector) Reset(batchNum common.BatchNum, fromSynchronizer bool) error {
70 return tracerr.Wrap(txsel.localAccountsDB.Reset(batchNum, fromSynchronizer))
71}
72
73func (txsel *TxSelector) getCoordIdx(tokenID common.TokenID) (common.Idx, error) {
74 return txsel.localAccountsDB.GetIdxByEthAddrBJJ(txsel.coordAccount.Addr,
75 txsel.coordAccount.BJJ, tokenID)
76}
77
78// coordAccountForTokenID creates a new L1CoordinatorTx to create a new
79// Coordinator account for the given TokenID in the case that the account does
80// not exist yet in the db, and does not exist a L1CoordinatorTx to creat that
81// account in the given array of L1CoordinatorTxs. If a new Coordinator account
82// needs to be created, a new L1CoordinatorTx will be returned from this
83// function. After calling this method, if the l1CoordinatorTx is added to the
84// selection, positionL1 must be increased 1.
85func (txsel *TxSelector) coordAccountForTokenID(l1CoordinatorTxs []common.L1Tx,
86 tokenID common.TokenID, positionL1 int) (*common.L1Tx, int, error) {
87 // check if CoordinatorAccount for TokenID is already pending to create
88 if checkAlreadyPendingToCreate(l1CoordinatorTxs, tokenID,
89 txsel.coordAccount.Addr, txsel.coordAccount.BJJ) {
90 return nil, positionL1, nil
91 }
92 _, err := txsel.getCoordIdx(tokenID)
93 if tracerr.Unwrap(err) == statedb.ErrIdxNotFound {
94 // create L1CoordinatorTx to create new CoordAccount for
95 // TokenID
96 l1CoordinatorTx := common.L1Tx{
97 Position: positionL1,
98 UserOrigin: false,
99 FromEthAddr: txsel.coordAccount.Addr,
100 FromBJJ: txsel.coordAccount.BJJ,
101 TokenID: tokenID,
102 Amount: big.NewInt(0),
103 DepositAmount: big.NewInt(0),
104 Type: common.TxTypeCreateAccountDeposit,
105 }
106
107 return &l1CoordinatorTx, positionL1, nil
108 }
109 if err != nil {
110 return nil, positionL1, tracerr.Wrap(err)
111 }
112 // CoordAccount for TokenID already exists
113 return nil, positionL1, nil
114}
115
116// GetL2TxSelection returns the L1CoordinatorTxs and a selection of the L2Txs
117// for the next batch, from the L2DB pool.
118// It returns: the CoordinatorIdxs used to receive the fees of the selected
119// L2Txs. An array of bytearrays with the signatures of the
120// AccountCreationAuthorization of the accounts of the users created by the
121// Coordinator with L1CoordinatorTxs of those accounts that does not exist yet
122// but there is a transactions to them and the authorization of account
123// creation exists. The L1UserTxs, L1CoordinatorTxs, PoolL2Txs that will be
124// included in the next batch.
125func (txsel *TxSelector) GetL2TxSelection(selectionConfig txprocessor.Config) ([]common.Idx,
126 [][]byte, []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
127 metric.GetL2TxSelection.Inc()
128 coordIdxs, accCreationAuths, _, l1CoordinatorTxs, l2Txs,
129 discardedL2Txs, err := txsel.getL1L2TxSelection(selectionConfig, []common.L1Tx{})
130 return coordIdxs, accCreationAuths, l1CoordinatorTxs, l2Txs,
131 discardedL2Txs, tracerr.Wrap(err)
132}
133
134// GetL1L2TxSelection returns the selection of L1 + L2 txs.
135// It returns: the CoordinatorIdxs used to receive the fees of the selected
136// L2Txs. An array of bytearrays with the signatures of the
137// AccountCreationAuthorization of the accounts of the users created by the
138// Coordinator with L1CoordinatorTxs of those accounts that does not exist yet
139// but there is a transactions to them and the authorization of account
140// creation exists. The L1UserTxs, L1CoordinatorTxs, PoolL2Txs that will be
141// included in the next batch.
142func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig txprocessor.Config,
143 l1UserTxs []common.L1Tx) ([]common.Idx, [][]byte, []common.L1Tx,
144 []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
145 metric.GetL1L2TxSelection.Inc()
146 coordIdxs, accCreationAuths, l1UserTxs, l1CoordinatorTxs, l2Txs,
147 discardedL2Txs, err := txsel.getL1L2TxSelection(selectionConfig, l1UserTxs)
148 return coordIdxs, accCreationAuths, l1UserTxs, l1CoordinatorTxs, l2Txs,
149 discardedL2Txs, tracerr.Wrap(err)
150}
151
152// getL1L2TxSelection returns the selection of L1 + L2 txs.
153// It returns: the CoordinatorIdxs used to receive the fees of the selected
154// L2Txs. An array of bytearrays with the signatures of the
155// AccountCreationAuthorization of the accounts of the users created by the
156// Coordinator with L1CoordinatorTxs of those accounts that does not exist yet
157// but there is a transactions to them and the authorization of account
158// creation exists. The L1UserTxs, L1CoordinatorTxs, PoolL2Txs that will be
159// included in the next batch.
160func (txsel *TxSelector) getL1L2TxSelection(selectionConfig txprocessor.Config,
161 l1UserTxs []common.L1Tx) ([]common.Idx, [][]byte, []common.L1Tx,
162 []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
163 // WIP.0: the TxSelector is not optimized and will need a redesign. The
164 // current version is implemented in order to have a functional
165 // implementation that can be used ASAP.
166
167 // Steps of this method:
168 // - ProcessL1Txs (User txs)
169 // - getPendingTxs (forgable directly with current state & not forgable
170 // yet)
171 // - split between l2TxsForgable & l2TxsNonForgable, where:
172 // - l2TxsForgable are the txs that are directly forgable with the
173 // current state
174 // - l2TxsNonForgable are the txs that are not directly forgable
175 // with the current state, but that may be forgable once the
176 // l2TxsForgable ones are processed
177 // - for l2TxsForgable, and if needed, for l2TxsNonForgable:
178 // - sort by Fee & Nonce
179 // - loop over l2Txs (txsel.processL2Txs)
180 // - Fill tx.TokenID tx.Nonce
181 // - Check enough Balance on sender
182 // - Check Nonce
183 // - Create CoordAccount L1CoordTx for TokenID if needed
184 // - & ProcessL1Tx of L1CoordTx
185 // - Check validity of receiver Account for ToEthAddr / ToBJJ
186 // - Create UserAccount L1CoordTx if needed (and possible)
187 // - If everything is fine, store l2Tx to validTxs & update NoncesMap
188 // - Prepare coordIdxsMap & AccumulatedFees
189 // - Distribute AccumulatedFees to CoordIdxs
190 // - MakeCheckpoint
191
192 txselStateDB := txsel.localAccountsDB.StateDB
193 tp := txprocessor.NewTxProcessor(txselStateDB, selectionConfig)
194 tp.AccumulatedFees = make(map[common.Idx]*big.Int)
195
196 // Process L1UserTxs
197 for i := 0; i < len(l1UserTxs); i++ {
198 // assumption: l1usertx are sorted by L1Tx.Position
199 _, _, _, _, err := tp.ProcessL1Tx(nil, &l1UserTxs[i])
200 if err != nil {
201 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
202 }
203 }
204
205 l2TxsFromDB, err := txsel.l2db.GetPendingTxs()
206 if err != nil {
207 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
208 }
209 l2TxsForgable, l2TxsNonForgable := splitL2ForgableAndNonForgable(tp, l2TxsFromDB)
210
211 // in case that length of l2TxsForgable is 0, no need to continue, there
212 // is no L2Txs to forge at all
213 if len(l2TxsForgable) == 0 {
214 var discardedL2Txs []common.PoolL2Tx
215 for i := 0; i < len(l2TxsNonForgable); i++ {
216 l2TxsNonForgable[i].Info =
217 "Tx not selected due impossibility to be forged with the current state"
218 discardedL2Txs = append(discardedL2Txs, l2TxsNonForgable[i])
219 }
220 err = tp.StateDB().MakeCheckpoint()
221 if err != nil {
222 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
223 }
224
225 metric.SelectedL1UserTxs.Set(float64(len(l1UserTxs)))
226 metric.SelectedL1CoordinatorTxs.Set(0)
227 metric.SelectedL2Txs.Set(0)
228 metric.DiscardedL2Txs.Set(float64(len(discardedL2Txs)))
229
230 return nil, nil, l1UserTxs, nil, nil, discardedL2Txs, nil
231 }
232
233 var accAuths [][]byte
234 var l1CoordinatorTxs []common.L1Tx
235 var validTxs, discardedL2Txs []common.PoolL2Tx
236 l2TxsForgable = sortL2Txs(l2TxsForgable)
237 accAuths, l1CoordinatorTxs, validTxs, discardedL2Txs, err =
238 txsel.processL2Txs(tp, selectionConfig, len(l1UserTxs),
239 l2TxsForgable, validTxs, discardedL2Txs)
240 if err != nil {
241 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
242 }
243
244 // if there is space for more txs get also the NonForgable txs, that may
245 // be unblocked once the Forgable ones are processed
246 if len(validTxs) < int(selectionConfig.MaxTx)-(len(l1UserTxs)+len(l1CoordinatorTxs)) {
247 l2TxsNonForgable = sortL2Txs(l2TxsNonForgable)
248 var accAuths2 [][]byte
249 var l1CoordinatorTxs2 []common.L1Tx
250 accAuths2, l1CoordinatorTxs2, validTxs, discardedL2Txs, err =
251 txsel.processL2Txs(tp, selectionConfig,
252 len(l1UserTxs)+len(l1CoordinatorTxs), l2TxsNonForgable,
253 validTxs, discardedL2Txs)
254 if err != nil {
255 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
256 }
257
258 accAuths = append(accAuths, accAuths2...)
259 l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTxs2...)
260 } else {
261 // if there is no space for NonForgable txs, put them at the
262 // discardedL2Txs array
263 for i := 0; i < len(l2TxsNonForgable); i++ {
264 l2TxsNonForgable[i].Info =
265 "Tx not selected due not available slots for L2Txs"
266 discardedL2Txs = append(discardedL2Txs, l2TxsNonForgable[i])
267 }
268 }
269
270 // get CoordIdxsMap for the TokenIDs
271 coordIdxsMap := make(map[common.TokenID]common.Idx)
272 for i := 0; i < len(validTxs); i++ {
273 // get TokenID from tx.Sender
274 accSender, err := tp.StateDB().GetAccount(validTxs[i].FromIdx)
275 if err != nil {
276 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
277 }
278 tokenID := accSender.TokenID
279
280 coordIdx, err := txsel.getCoordIdx(tokenID)
281 if err != nil {
282 // if err is db.ErrNotFound, should not happen, as all
283 // the validTxs.TokenID should have a CoordinatorIdx
284 // created in the DB at this point
285 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
286 }
287 coordIdxsMap[tokenID] = coordIdx
288 }
289
290 var coordIdxs []common.Idx
291 for _, idx := range coordIdxsMap {
292 coordIdxs = append(coordIdxs, idx)
293 }
294 // sort CoordIdxs
295 sort.SliceStable(coordIdxs, func(i, j int) bool {
296 return coordIdxs[i] < coordIdxs[j]
297 })
298
299 // distribute the AccumulatedFees from the processed L2Txs into the
300 // Coordinator Idxs
301 for idx, accumulatedFee := range tp.AccumulatedFees {
302 cmp := accumulatedFee.Cmp(big.NewInt(0))
303 if cmp == 1 { // accumulatedFee>0
304 // send the fee to the Idx of the Coordinator for the TokenID
305 accCoord, err := txsel.localAccountsDB.GetAccount(idx)
306 if err != nil {
307 log.Errorw("Can not distribute accumulated fees to coordinator "+
308 "account: No coord Idx to receive fee", "idx", idx)
309 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
310 }
311 accCoord.Balance = new(big.Int).Add(accCoord.Balance, accumulatedFee)
312 _, err = txsel.localAccountsDB.UpdateAccount(idx, accCoord)
313 if err != nil {
314 log.Error(err)
315 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
316 }
317 }
318 }
319
320 err = tp.StateDB().MakeCheckpoint()
321 if err != nil {
322 return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
323 }
324
325 metric.SelectedL1CoordinatorTxs.Set(float64(len(l1CoordinatorTxs)))
326 metric.SelectedL1UserTxs.Set(float64(len(l1UserTxs)))
327 metric.SelectedL2Txs.Set(float64(len(validTxs)))
328 metric.DiscardedL2Txs.Set(float64(len(discardedL2Txs)))
329
330 return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, validTxs, discardedL2Txs, nil
331}
332
333func (txsel *TxSelector) processL2Txs(tp *txprocessor.TxProcessor,
334 selectionConfig txprocessor.Config, nL1Txs int, l2Txs, validTxs, discardedL2Txs []common.PoolL2Tx) (
335 [][]byte, []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
336 var l1CoordinatorTxs []common.L1Tx
337 positionL1 := nL1Txs
338 var accAuths [][]byte
339 // Iterate over l2Txs
340 // - check Nonces
341 // - check enough Balance for the Amount+Fee
342 // - if needed, create new L1CoordinatorTxs for unexisting ToIdx
343 // - keep used accAuths
344 // - put the valid txs into validTxs array
345 for i := 0; i < len(l2Txs); i++ {
346 // Check if there is space for more L2Txs in the selection
347 maxL2Txs := int(selectionConfig.MaxTx) - nL1Txs - len(l1CoordinatorTxs)
348 if len(validTxs) >= maxL2Txs {
349 // no more available slots for L2Txs, so mark this tx
350 // but also the rest of remaining txs as discarded
351 for j := i; j < len(l2Txs); j++ {
352 l2Txs[j].Info =
353 "Tx not selected due not available slots for L2Txs"
354 discardedL2Txs = append(discardedL2Txs, l2Txs[j])
355 }
356 break
357 }
358
359 // get Nonce & TokenID from the Account by l2Tx.FromIdx
360 accSender, err := tp.StateDB().GetAccount(l2Txs[i].FromIdx)
361 if err != nil {
362 return nil, nil, nil, nil, tracerr.Wrap(err)
363 }
364 l2Txs[i].TokenID = accSender.TokenID
365
366 // Check enough Balance on sender
367 enoughBalance, balance, feeAndAmount := tp.CheckEnoughBalance(l2Txs[i])
368 if !enoughBalance {
369 // not valid Amount with current Balance. Discard L2Tx,
370 // and update Info parameter of the tx, and add it to
371 // the discardedTxs array
372 l2Txs[i].Info = fmt.Sprintf("Tx not selected due to not enough Balance at the sender. "+
373 "Current sender account Balance: %s, Amount+Fee: %s",
374 balance.String(), feeAndAmount.String())
375 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
376 continue
377 }
378
379 // Check if Nonce is correct
380 if l2Txs[i].Nonce != accSender.Nonce {
381 // not valid Nonce at tx. Discard L2Tx, and update Info
382 // parameter of the tx, and add it to the discardedTxs
383 // array
384 l2Txs[i].Info = fmt.Sprintf("Tx not selected due to not current Nonce. "+
385 "Tx.Nonce: %d, Account.Nonce: %d", l2Txs[i].Nonce, accSender.Nonce)
386 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
387 continue
388 }
389
390 // if TokenID does not exist yet, create new L1CoordinatorTx to
391 // create the CoordinatorAccount for that TokenID, to receive
392 // the fees. Only in the case that there does not exist yet a
393 // pending L1CoordinatorTx to create the account for the
394 // Coordinator for that TokenID
395 var newL1CoordTx *common.L1Tx
396 newL1CoordTx, positionL1, err =
397 txsel.coordAccountForTokenID(l1CoordinatorTxs,
398 accSender.TokenID, positionL1)
399 if err != nil {
400 return nil, nil, nil, nil, tracerr.Wrap(err)
401 }
402 if newL1CoordTx != nil {
403 // if there is no space for the L1CoordinatorTx as MaxL1Tx, or no space
404 // for L1CoordinatorTx + L2Tx as MaxTx, discard the L2Tx
405 if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-nL1Txs ||
406 len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-nL1Txs {
407 // discard L2Tx, and update Info parameter of
408 // the tx, and add it to the discardedTxs array
409 l2Txs[i].Info = "Tx not selected because the L2Tx depends on a " +
410 "L1CoordinatorTx and there is not enough space for L1Coordinator"
411 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
412 continue
413 }
414 // increase positionL1
415 positionL1++
416 l1CoordinatorTxs = append(l1CoordinatorTxs, *newL1CoordTx)
417 accAuths = append(accAuths, txsel.coordAccount.AccountCreationAuth)
418
419 // process the L1CoordTx
420 _, _, _, _, err := tp.ProcessL1Tx(nil, newL1CoordTx)
421 if err != nil {
422 return nil, nil, nil, nil, tracerr.Wrap(err)
423 }
424 }
425
426 // If tx.ToIdx>=256, tx.ToIdx should exist to localAccountsDB,
427 // if so, tx is used. If tx.ToIdx==0, for an L2Tx will be the
428 // case of TxToEthAddr or TxToBJJ, check if
429 // tx.ToEthAddr/tx.ToBJJ exist in localAccountsDB, if yes tx is
430 // used; if not, check if tx.ToEthAddr is in
431 // AccountCreationAuthDB, if so, tx is used and L1CoordinatorTx
432 // of CreateAccountAndDeposit is created. If tx.ToIdx==1, is a
433 // Exit type and is used.
434 if l2Txs[i].ToIdx == 0 { // ToEthAddr/ToBJJ case
435 validL2Tx, l1CoordinatorTx, accAuth, err :=
436 txsel.processTxToEthAddrBJJ(validTxs, selectionConfig,
437 nL1Txs, l1CoordinatorTxs, positionL1, l2Txs[i])
438 if err != nil {
439 log.Debugw("txsel.processTxToEthAddrBJJ", "err", err)
440 // Discard L2Tx, and update Info parameter of
441 // the tx, and add it to the discardedTxs array
442 l2Txs[i].Info = fmt.Sprintf("Tx not selected (in processTxToEthAddrBJJ) due to %s",
443 err.Error())
444 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
445 continue
446 }
447 // if there is no space for the L1CoordinatorTx as MaxL1Tx, or no space
448 // for L1CoordinatorTx + L2Tx as MaxTx, discard the L2Tx
449 if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-nL1Txs ||
450 len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-nL1Txs {
451 // discard L2Tx, and update Info parameter of
452 // the tx, and add it to the discardedTxs array
453 l2Txs[i].Info = "Tx not selected because the L2Tx depends on a " +
454 "L1CoordinatorTx and there is not enough space for L1Coordinator"
455 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
456 continue
457 }
458
459 if l1CoordinatorTx != nil && validL2Tx != nil {
460 // If ToEthAddr == 0xff.. this means that we
461 // are handling a TransferToBJJ, which doesn't
462 // require an authorization because it doesn't
463 // contain a valid ethereum address.
464 // Otherwise only create the account if we have
465 // the corresponding authorization
466 if validL2Tx.ToEthAddr == common.FFAddr {
467 accAuths = append(accAuths, common.EmptyEthSignature)
468 l1CoordinatorTxs = append(l1CoordinatorTxs, *l1CoordinatorTx)
469 positionL1++
470 } else if accAuth != nil {
471 accAuths = append(accAuths, accAuth.Signature)
472 l1CoordinatorTxs = append(l1CoordinatorTxs, *l1CoordinatorTx)
473 positionL1++
474 }
475
476 // process the L1CoordTx
477 _, _, _, _, err := tp.ProcessL1Tx(nil, l1CoordinatorTx)
478 if err != nil {
479 return nil, nil, nil, nil, tracerr.Wrap(err)
480 }
481 }
482 if validL2Tx == nil {
483 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
484 continue
485 }
486 } else if l2Txs[i].ToIdx >= common.IdxUserThreshold {
487 receiverAcc, err := txsel.localAccountsDB.GetAccount(l2Txs[i].ToIdx)
488 if err != nil {
489 // tx not valid
490 log.Debugw("invalid L2Tx: ToIdx not found in StateDB",
491 "ToIdx", l2Txs[i].ToIdx)
492 // Discard L2Tx, and update Info parameter of
493 // the tx, and add it to the discardedTxs array
494 l2Txs[i].Info = fmt.Sprintf("Tx not selected due to tx.ToIdx not found in StateDB. "+
495 "ToIdx: %d", l2Txs[i].ToIdx)
496 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
497 continue
498 }
499 if l2Txs[i].ToEthAddr != common.EmptyAddr {
500 if l2Txs[i].ToEthAddr != receiverAcc.EthAddr {
501 log.Debugw("invalid L2Tx: ToEthAddr does not correspond to the Account.EthAddr",
502 "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr",
503 l2Txs[i].ToEthAddr, "account.EthAddr", receiverAcc.EthAddr)
504 // Discard L2Tx, and update Info
505 // parameter of the tx, and add it to
506 // the discardedTxs array
507 l2Txs[i].Info = fmt.Sprintf("Tx not selected because ToEthAddr "+
508 "does not correspond to the Account.EthAddr. "+
509 "tx.ToIdx: %d, tx.ToEthAddr: %s, account.EthAddr: %s",
510 l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, receiverAcc.EthAddr)
511 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
512 continue
513 }
514 }
515 if l2Txs[i].ToBJJ != common.EmptyBJJComp {
516 if l2Txs[i].ToBJJ != receiverAcc.BJJ {
517 log.Debugw("invalid L2Tx: ToBJJ does not correspond to the Account.BJJ",
518 "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr", l2Txs[i].ToBJJ,
519 "account.BJJ", receiverAcc.BJJ)
520 // Discard L2Tx, and update Info
521 // parameter of the tx, and add it to
522 // the discardedTxs array
523 l2Txs[i].Info = fmt.Sprintf("Tx not selected because tx.ToBJJ "+
524 "does not correspond to the Account.BJJ. "+
525 "tx.ToIdx: %d, tx.ToEthAddr: %s, tx.ToBJJ: %s, account.BJJ: %s",
526 l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, l2Txs[i].ToBJJ, receiverAcc.BJJ)
527 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
528 continue
529 }
530 }
531 }
532
533 // get CoordIdxsMap for the TokenID of the current l2Txs[i]
534 // get TokenID from tx.Sender account
535 tokenID := accSender.TokenID
536 coordIdx, err := txsel.getCoordIdx(tokenID)
537 if err != nil {
538 // if err is db.ErrNotFound, should not happen, as all
539 // the validTxs.TokenID should have a CoordinatorIdx
540 // created in the DB at this point
541 return nil, nil, nil, nil,
542 tracerr.Wrap(fmt.Errorf("Could not get CoordIdx for TokenID=%d, "+
543 "due: %s", tokenID, err))
544 }
545 // prepare temp coordIdxsMap & AccumulatedFees for the call to
546 // ProcessL2Tx
547 coordIdxsMap := map[common.TokenID]common.Idx{tokenID: coordIdx}
548 // tp.AccumulatedFees = make(map[common.Idx]*big.Int)
549 if _, ok := tp.AccumulatedFees[coordIdx]; !ok {
550 tp.AccumulatedFees[coordIdx] = big.NewInt(0)
551 }
552
553 _, _, _, err = tp.ProcessL2Tx(coordIdxsMap, nil, nil, &l2Txs[i])
554 if err != nil {
555 log.Debugw("txselector.getL1L2TxSelection at ProcessL2Tx", "err", err)
556 // Discard L2Tx, and update Info parameter of the tx,
557 // and add it to the discardedTxs array
558 l2Txs[i].Info = fmt.Sprintf("Tx not selected (in ProcessL2Tx) due to %s",
559 err.Error())
560 discardedL2Txs = append(discardedL2Txs, l2Txs[i])
561 continue
562 }
563
564 validTxs = append(validTxs, l2Txs[i])
565 } // after this loop, no checks to discard txs should be done
566
567 return accAuths, l1CoordinatorTxs, validTxs, discardedL2Txs, nil
568}
569
570// processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where
571// ToIdx==0, which can be the tx type of ToEthAddr or ToBJJ. If the receiver
572// does not have an account yet, a new L1CoordinatorTx of type
573// CreateAccountDeposit (with 0 as DepositAmount) is created and added to the
574// l1CoordinatorTxs array, and then the PoolL2Tx is added into the validTxs
575// array.
576func (txsel *TxSelector) processTxToEthAddrBJJ(validTxs []common.PoolL2Tx,
577 selectionConfig txprocessor.Config, nL1UserTxs int, l1CoordinatorTxs []common.L1Tx,
578 positionL1 int, l2Tx common.PoolL2Tx) (*common.PoolL2Tx, *common.L1Tx,
579 *common.AccountCreationAuth, error) {
580 // if L2Tx needs a new L1CoordinatorTx of CreateAccount type, and a
581 // previous L2Tx in the current process already created a
582 // L1CoordinatorTx of this type, in the DB there still seem that needs
583 // to create a new L1CoordinatorTx, but as is already created, the tx
584 // is valid
585 if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2Tx.TokenID, l2Tx.ToEthAddr, l2Tx.ToBJJ) {
586 return &l2Tx, nil, nil, nil
587 }
588
589 var l1CoordinatorTx *common.L1Tx
590 var accAuth *common.AccountCreationAuth
591 if l2Tx.ToEthAddr != common.EmptyAddr && l2Tx.ToEthAddr != common.FFAddr {
592 // case: ToEthAddr != 0x00 neither 0xff
593 if l2Tx.ToBJJ != common.EmptyBJJComp {
594 // case: ToBJJ!=0:
595 // if idx exist for EthAddr&BJJ use it
596 _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr,
597 l2Tx.ToBJJ, l2Tx.TokenID)
598 if err == nil {
599 // account for ToEthAddr&ToBJJ already exist,
600 // there is no need to create a new one.
601 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
602 return &l2Tx, nil, nil, nil
603 }
604 // if not, check if AccountCreationAuth exist for that
605 // ToEthAddr
606 accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
607 if err != nil {
608 // not found, l2Tx will not be added in the selection
609 return nil, nil, nil,
610 tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found "+
611 "in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
612 l2Tx.ToIdx, l2Tx.ToEthAddr.Hex()))
613 }
614 if accAuth.BJJ != l2Tx.ToBJJ {
615 // if AccountCreationAuth.BJJ is not the same
616 // than in the tx, tx is not accepted
617 return nil, nil, nil,
618 tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in StateDB, "+
619 "neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB. "+
620 "ToIdx: %d, ToEthAddr: %s, ToBJJ: %s",
621 l2Tx.ToIdx, l2Tx.ToEthAddr.Hex(), l2Tx.ToBJJ.String()))
622 }
623 } else {
624 // case: ToBJJ==0:
625 // if idx exist for EthAddr use it
626 _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2Tx.ToEthAddr, l2Tx.TokenID)
627 if err == nil {
628 // account for ToEthAddr already exist,
629 // there is no need to create a new one.
630 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
631 return &l2Tx, nil, nil, nil
632 }
633 // if not, check if AccountCreationAuth exist for that ToEthAddr
634 accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
635 if err != nil {
636 // not found, l2Tx will not be added in the selection
637 return nil, nil, nil,
638 tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in "+
639 "StateDB, neither ToEthAddr found in "+
640 "AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
641 l2Tx.ToIdx, l2Tx.ToEthAddr))
642 }
643 }
644 // create L1CoordinatorTx for the accountCreation
645 l1CoordinatorTx = &common.L1Tx{
646 Position: positionL1,
647 UserOrigin: false,
648 FromEthAddr: accAuth.EthAddr,
649 FromBJJ: accAuth.BJJ,
650 TokenID: l2Tx.TokenID,
651 Amount: big.NewInt(0),
652 DepositAmount: big.NewInt(0),
653 Type: common.TxTypeCreateAccountDeposit,
654 }
655 } else if l2Tx.ToEthAddr == common.FFAddr && l2Tx.ToBJJ != common.EmptyBJJComp {
656 // if idx exist for EthAddr&BJJ use it
657 _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr, l2Tx.ToBJJ,
658 l2Tx.TokenID)
659 if err == nil {
660 // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
661 // there is no need to create a new one.
662 // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
663 return &l2Tx, nil, nil, nil
664 }
665 // if idx don't exist for EthAddr&BJJ, coordinator can create a
666 // new account without L1Authorization, as ToEthAddr==0xff
667 // create L1CoordinatorTx for the accountCreation
668 l1CoordinatorTx = &common.L1Tx{
669 Position: positionL1,
670 UserOrigin: false,
671 FromEthAddr: l2Tx.ToEthAddr,
672 FromBJJ: l2Tx.ToBJJ,
673 TokenID: l2Tx.TokenID,
674 Amount: big.NewInt(0),
675 DepositAmount: big.NewInt(0),
676 Type: common.TxTypeCreateAccountDeposit,
677 }
678 }
679 // if there is no space for the L1CoordinatorTx as MaxL1Tx, or no space
680 // for L1CoordinatorTx + L2Tx as MaxTx, discard the L2Tx
681 if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1Tx)-nL1UserTxs ||
682 len(l1CoordinatorTxs)+1 >= int(selectionConfig.MaxTx)-nL1UserTxs {
683 // L2Tx discarded
684 return nil, nil, nil, tracerr.Wrap(fmt.Errorf("L2Tx discarded due to no available slots " +
685 "for L1CoordinatorTx to create a new account for receiver of L2Tx"))
686 }
687
688 return &l2Tx, l1CoordinatorTx, accAuth, nil
689}
690
691func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx, tokenID common.TokenID,
692 addr ethCommon.Address, bjj babyjub.PublicKeyComp) bool {
693 for i := 0; i < len(l1CoordinatorTxs); i++ {
694 if l1CoordinatorTxs[i].FromEthAddr == addr &&
695 l1CoordinatorTxs[i].TokenID == tokenID &&
696 l1CoordinatorTxs[i].FromBJJ == bjj {
697 return true
698 }
699 }
700 return false
701}
702
703// sortL2Txs sorts the PoolL2Txs by AbsoluteFee and then by Nonce
704func sortL2Txs(l2Txs []common.PoolL2Tx) []common.PoolL2Tx {
705 // Sort by absolute fee with SliceStable, so that txs with same
706 // AbsoluteFee are not rearranged and nonce order is kept in such case
707 sort.SliceStable(l2Txs, func(i, j int) bool {
708 return l2Txs[i].AbsoluteFee > l2Txs[j].AbsoluteFee
709 })
710
711 // sort l2Txs by Nonce. This can be done in many different ways, what
712 // is needed is to output the l2Txs where the Nonce of l2Txs for each
713 // Account is sorted, but the l2Txs can not be grouped by sender Account
714 // neither by Fee. This is because later on the Nonces will need to be
715 // sequential for the zkproof generation.
716 sort.Slice(l2Txs, func(i, j int) bool {
717 return l2Txs[i].Nonce < l2Txs[j].Nonce
718 })
719
720 return l2Txs
721}
722
723func splitL2ForgableAndNonForgable(tp *txprocessor.TxProcessor,
724 l2Txs []common.PoolL2Tx) ([]common.PoolL2Tx, []common.PoolL2Tx) {
725 var l2TxsForgable, l2TxsNonForgable []common.PoolL2Tx
726 for i := 0; i < len(l2Txs); i++ {
727 accSender, err := tp.StateDB().GetAccount(l2Txs[i].FromIdx)
728 if err != nil {
729 l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i])
730 continue
731 }
732
733 if l2Txs[i].Nonce != accSender.Nonce {
734 l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i])
735 continue
736 }
737 enoughBalance, _, _ := tp.CheckEnoughBalance(l2Txs[i])
738 if !enoughBalance {
739 l2TxsNonForgable = append(l2TxsNonForgable, l2Txs[i])
740 continue
741 }
742 l2TxsForgable = append(l2TxsForgable, l2Txs[i])
743 }
744 return l2TxsForgable, l2TxsNonForgable
745}