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