You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

636 lines
24 KiB

Update coordinator to work better under real net - cli / node - Update handler of SIGINT so that after 3 SIGINTs, the process terminates unconditionally - coordinator - Store stats without pointer - In all functions that send a variable via channel, check for context done to avoid deadlock (due to no process reading from the channel, which has no queue) when the node is stopped. - Abstract `canForge` so that it can be used outside of the `Coordinator` - In `canForge` check the blockNumber in current and next slot. - Update tests due to smart contract changes in slot handling, and minimum bid defaults - TxManager - Add consts, vars and stats to allow evaluating `canForge` - Add `canForge` method (not used yet) - Store batch and nonces status (last success and last pending) - Track nonces internally instead of relying on the ethereum node (this is required to work with ganache when there are pending txs) - Handle the (common) case of the receipt not being found after the tx is sent. - Don't start the main loop until we get an initial messae fo the stats and vars (so that in the loop the stats and vars are set to synchronizer values) - When a tx fails, check and discard all the failed transactions before sending the message to stop the pipeline. This will avoid sending consecutive messages of stop the pipeline when multiple txs are detected to be failed consecutively. Also, future txs of the same pipeline after a discarded txs are discarded, and their nonces reused. - Robust handling of nonces: - If geth returns nonce is too low, increase it - If geth returns nonce too hight, decrease it - If geth returns underpriced, increase gas price - If geth returns replace underpriced, increase gas price - Add support for resending transactions after a timeout - Store `BatchInfos` in a queue - Pipeline - When an error is found, stop forging batches and send a message to the coordinator to stop the pipeline with information of the failed batch number so that in a restart, non-failed batches are not repated. - When doing a reset of the stateDB, if possible reset from the local checkpoint instead of resetting from the synchronizer. This allows resetting from a batch that is valid but not yet sent / synced. - Every time a pipeline is started, assign it a number from a counter. This allows the TxManager to ignore batches from stopped pipelines, via a message sent by the coordinator. - Avoid forging when we haven't reached the rollup genesis block number. - Add config parameter `StartSlotBlocksDelay`: StartSlotBlocksDelay is the number of blocks of delay to wait before starting the pipeline when we reach a slot in which we can forge. - When detecting a reorg, only reset the pipeline if the batch from which the pipeline started changed and wasn't sent by us. - Add config parameter `ScheduleBatchBlocksAheadCheck`: ScheduleBatchBlocksAheadCheck is the number of blocks ahead in which the forger address is checked to be allowed to forge (apart from checking the next block), used to decide when to stop scheduling new batches (by stopping the pipeline). For example, if we are at block 10 and ScheduleBatchBlocksAheadCheck is 5, eventhough at block 11 we canForge, the pipeline will be stopped if we can't forge at block 15. This value should be the expected number of blocks it takes between scheduling a batch and having it mined. - Add config parameter `SendBatchBlocksMarginCheck`: SendBatchBlocksMarginCheck is the number of margin blocks ahead in which the coordinator is also checked to be allowed to forge, apart from the next block; used to decide when to stop sending batches to the smart contract. For example, if we are at block 10 and SendBatchBlocksMarginCheck is 5, eventhough at block 11 we canForge, the batch will be discarded if we can't forge at block 15. - Add config parameter `TxResendTimeout`: TxResendTimeout is the timeout after which a non-mined ethereum transaction will be resent (reusing the nonce) with a newly calculated gas price - Add config parameter `MaxGasPrice`: MaxGasPrice is the maximum gas price allowed for ethereum transactions - Add config parameter `NoReuseNonce`: NoReuseNonce disables reusing nonces of pending transactions for new replacement transactions. This is useful for testing with Ganache. - Extend BatchInfo with more useful information for debugging - eth / ethereum client - Add necessary methods to create the auth object for transactions manually so that we can set the nonce, gas price, gas limit, etc manually - Update `RollupForgeBatch` to take an auth object as input (so that the coordinator can set parameters manually) - synchronizer - In stats, add `NextSlot` - In stats, store full last batch instead of just last batch number - Instead of calculating a nextSlot from scratch every time, update the current struct (only updating the forger info if we are Synced) - Afer every processed batch, check that the calculated StateDB MTRoot matches the StateRoot found in the forgeBatch event.
3 years ago
  1. package txselector
  2. // current: very simple version of TxSelector
  3. import (
  4. "fmt"
  5. "math/big"
  6. "sort"
  7. ethCommon "github.com/ethereum/go-ethereum/common"
  8. "github.com/hermeznetwork/hermez-node/common"
  9. "github.com/hermeznetwork/hermez-node/db/kvdb"
  10. "github.com/hermeznetwork/hermez-node/db/l2db"
  11. "github.com/hermeznetwork/hermez-node/db/statedb"
  12. "github.com/hermeznetwork/hermez-node/log"
  13. "github.com/hermeznetwork/hermez-node/txprocessor"
  14. "github.com/hermeznetwork/tracerr"
  15. "github.com/iden3/go-iden3-crypto/babyjub"
  16. )
  17. // CoordAccount contains the data of the Coordinator account, that will be used
  18. // to create new transactions of CreateAccountDeposit type to add new TokenID
  19. // accounts for the Coordinator to receive the fees.
  20. type CoordAccount struct {
  21. Addr ethCommon.Address
  22. BJJ babyjub.PublicKeyComp
  23. AccountCreationAuth []byte // signature in byte array format
  24. }
  25. // SelectionConfig contains the parameters of configuration of the selection of
  26. // transactions for the next batch
  27. type SelectionConfig struct {
  28. // MaxL1UserTxs is the maximum L1-user-tx for a batch
  29. MaxL1UserTxs uint64
  30. // TxProcessorConfig contains the config for ProcessTxs
  31. TxProcessorConfig txprocessor.Config
  32. }
  33. // TxSelector implements all the functionalities to select the txs for the next
  34. // batch
  35. type TxSelector struct {
  36. l2db *l2db.L2DB
  37. localAccountsDB *statedb.LocalStateDB
  38. coordAccount *CoordAccount
  39. }
  40. // NewTxSelector returns a *TxSelector
  41. func 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. return &TxSelector{
  55. l2db: l2,
  56. localAccountsDB: localAccountsDB,
  57. coordAccount: coordAccount,
  58. }, nil
  59. }
  60. // LocalAccountsDB returns the LocalStateDB of the TxSelector
  61. func (txsel *TxSelector) LocalAccountsDB() *statedb.LocalStateDB {
  62. return txsel.localAccountsDB
  63. }
  64. // Reset tells the TxSelector to get it's internal AccountsDB
  65. // from the required `batchNum`
  66. func (txsel *TxSelector) Reset(batchNum common.BatchNum, fromSynchronizer bool) error {
  67. return tracerr.Wrap(txsel.localAccountsDB.Reset(batchNum, fromSynchronizer))
  68. }
  69. func (txsel *TxSelector) getCoordIdx(tokenID common.TokenID) (common.Idx, error) {
  70. return txsel.localAccountsDB.GetIdxByEthAddrBJJ(txsel.coordAccount.Addr,
  71. txsel.coordAccount.BJJ, tokenID)
  72. }
  73. // coordAccountForTokenID creates a new L1CoordinatorTx to create a new
  74. // Coordinator account for the given TokenID in the case that the account does
  75. // not exist yet in the db, and does not exist a L1CoordinatorTx to creat that
  76. // account in the given array of L1CoordinatorTxs. If a new Coordinator account
  77. // needs to be created, a new L1CoordinatorTx will be returned from this
  78. // function. After calling this method, if the l1CoordinatorTx is added to the
  79. // selection, positionL1 must be increased 1.
  80. func (txsel *TxSelector) coordAccountForTokenID(l1CoordinatorTxs []common.L1Tx,
  81. tokenID common.TokenID, positionL1 int) (*common.L1Tx, int, error) {
  82. // check if CoordinatorAccount for TokenID is already pending to create
  83. if checkAlreadyPendingToCreate(l1CoordinatorTxs, tokenID,
  84. txsel.coordAccount.Addr, txsel.coordAccount.BJJ) {
  85. return nil, positionL1, nil
  86. }
  87. _, err := txsel.getCoordIdx(tokenID)
  88. if tracerr.Unwrap(err) == statedb.ErrIdxNotFound {
  89. // create L1CoordinatorTx to create new CoordAccount for
  90. // TokenID
  91. l1CoordinatorTx := common.L1Tx{
  92. Position: positionL1,
  93. UserOrigin: false,
  94. FromEthAddr: txsel.coordAccount.Addr,
  95. FromBJJ: txsel.coordAccount.BJJ,
  96. TokenID: tokenID,
  97. Amount: big.NewInt(0),
  98. DepositAmount: big.NewInt(0),
  99. Type: common.TxTypeCreateAccountDeposit,
  100. }
  101. return &l1CoordinatorTx, positionL1, nil
  102. }
  103. if err != nil {
  104. return nil, positionL1, tracerr.Wrap(err)
  105. }
  106. // CoordAccount for TokenID already exists
  107. return nil, positionL1, nil
  108. }
  109. // GetL2TxSelection returns the L1CoordinatorTxs and a selection of the L2Txs
  110. // for the next batch, from the L2DB pool.
  111. // It returns: the CoordinatorIdxs used to receive the fees of the selected
  112. // L2Txs. An array of bytearrays with the signatures of the
  113. // AccountCreationAuthorization of the accounts of the users created by the
  114. // Coordinator with L1CoordinatorTxs of those accounts that does not exist yet
  115. // but there is a transactions to them and the authorization of account
  116. // creation exists. The L1UserTxs, L1CoordinatorTxs, PoolL2Txs that will be
  117. // included in the next batch.
  118. func (txsel *TxSelector) GetL2TxSelection(selectionConfig *SelectionConfig) ([]common.Idx,
  119. [][]byte, []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
  120. metricGetL2TxSelection.Inc()
  121. coordIdxs, accCreationAuths, _, l1CoordinatorTxs, l2Txs,
  122. discardedL2Txs, err := txsel.getL1L2TxSelection(selectionConfig, []common.L1Tx{})
  123. return coordIdxs, accCreationAuths, l1CoordinatorTxs, l2Txs,
  124. discardedL2Txs, tracerr.Wrap(err)
  125. }
  126. // GetL1L2TxSelection returns the selection of L1 + L2 txs.
  127. // It returns: the CoordinatorIdxs used to receive the fees of the selected
  128. // L2Txs. An array of bytearrays with the signatures of the
  129. // AccountCreationAuthorization of the accounts of the users created by the
  130. // Coordinator with L1CoordinatorTxs of those accounts that does not exist yet
  131. // but there is a transactions to them and the authorization of account
  132. // creation exists. The L1UserTxs, L1CoordinatorTxs, PoolL2Txs that will be
  133. // included in the next batch.
  134. func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig,
  135. l1UserTxs []common.L1Tx) ([]common.Idx, [][]byte, []common.L1Tx,
  136. []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
  137. metricGetL1L2TxSelection.Inc()
  138. coordIdxs, accCreationAuths, l1UserTxs, l1CoordinatorTxs, l2Txs,
  139. discardedL2Txs, err := txsel.getL1L2TxSelection(selectionConfig, l1UserTxs)
  140. return coordIdxs, accCreationAuths, l1UserTxs, l1CoordinatorTxs, l2Txs,
  141. discardedL2Txs, tracerr.Wrap(err)
  142. }
  143. func (txsel *TxSelector) getL1L2TxSelection(selectionConfig *SelectionConfig,
  144. l1UserTxs []common.L1Tx) ([]common.Idx, [][]byte, []common.L1Tx,
  145. []common.L1Tx, []common.PoolL2Tx, []common.PoolL2Tx, error) {
  146. // WIP.0: the TxSelector is not optimized and will need a redesign. The
  147. // current version is implemented in order to have a functional
  148. // implementation that can be used asap.
  149. //
  150. // WIP.1: this method uses a 'cherry-pick' of internal calls of the
  151. // StateDB, a refactor of the StateDB to reorganize it internally is
  152. // planned once the main functionallities are covered, with that
  153. // refactor the TxSelector will be updated also.
  154. // get pending l2-tx from tx-pool
  155. l2TxsRaw, err := txsel.l2db.GetPendingTxs()
  156. if err != nil {
  157. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  158. }
  159. txselStateDB := txsel.localAccountsDB.StateDB
  160. tp := txprocessor.NewTxProcessor(txselStateDB, selectionConfig.TxProcessorConfig)
  161. // Process L1UserTxs
  162. for i := 0; i < len(l1UserTxs); i++ {
  163. // assumption: l1usertx are sorted by L1Tx.Position
  164. _, _, _, _, err := tp.ProcessL1Tx(nil, &l1UserTxs[i])
  165. if err != nil {
  166. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  167. }
  168. }
  169. // discardedL2Txs contains an array of the L2Txs that have not been selected in this Batch
  170. var l1CoordinatorTxs []common.L1Tx
  171. positionL1 := len(l1UserTxs)
  172. var accAuths [][]byte
  173. // sort l2TxsRaw (cropping at MaxTx at this point)
  174. l2Txs0, discardedL2Txs := txsel.getL2Profitable(l2TxsRaw, selectionConfig.TxProcessorConfig.MaxTx)
  175. for i := range discardedL2Txs {
  176. discardedL2Txs[i].Info = "Tx not selected due to low absolute fee"
  177. }
  178. noncesMap := make(map[common.Idx]common.Nonce)
  179. var l2Txs []common.PoolL2Tx
  180. // iterate over l2Txs
  181. // - if tx.TokenID does not exist at CoordsIdxDB
  182. // - create new L1CoordinatorTx creating a CoordAccount, for
  183. // Coordinator to receive the fee of the new TokenID
  184. for i := 0; i < len(l2Txs0); i++ {
  185. accSender, err := tp.StateDB().GetAccount(l2Txs0[i].FromIdx)
  186. if err != nil {
  187. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  188. }
  189. l2Txs0[i].TokenID = accSender.TokenID
  190. // populate the noncesMap used at the next iteration
  191. noncesMap[l2Txs0[i].FromIdx] = accSender.Nonce
  192. // if TokenID does not exist yet, create new L1CoordinatorTx to
  193. // create the CoordinatorAccount for that TokenID, to receive
  194. // the fees. Only in the case that there does not exist yet a
  195. // pending L1CoordinatorTx to create the account for the
  196. // Coordinator for that TokenID
  197. var newL1CoordTx *common.L1Tx
  198. newL1CoordTx, positionL1, err =
  199. txsel.coordAccountForTokenID(l1CoordinatorTxs,
  200. accSender.TokenID, positionL1)
  201. if err != nil {
  202. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  203. }
  204. if newL1CoordTx != nil {
  205. // if there is no space for the L1CoordinatorTx, discard the L2Tx
  206. if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1UserTxs)-len(l1UserTxs) {
  207. // discard L2Tx, and update Info parameter of
  208. // the tx, and add it to the discardedTxs array
  209. l2Txs0[i].Info = "Tx not selected because the L2Tx depends on a " +
  210. "L1CoordinatorTx and there is not enough space for L1Coordinator"
  211. discardedL2Txs = append(discardedL2Txs, l2Txs0[i])
  212. continue
  213. }
  214. // increase positionL1
  215. positionL1++
  216. l1CoordinatorTxs = append(l1CoordinatorTxs, *newL1CoordTx)
  217. accAuths = append(accAuths, txsel.coordAccount.AccountCreationAuth)
  218. }
  219. l2Txs = append(l2Txs, l2Txs0[i])
  220. }
  221. var validTxs []common.PoolL2Tx
  222. // iterate over l2TxsRaw
  223. // - check Nonces
  224. // - check enough Balance for the Amount+Fee
  225. // - if needed, create new L1CoordinatorTxs for unexisting ToIdx
  226. // - keep used accAuths
  227. // - put the valid txs into validTxs array
  228. for i := 0; i < len(l2Txs); i++ {
  229. enoughBalance, balance, feeAndAmount := tp.CheckEnoughBalance(l2Txs[i])
  230. if !enoughBalance {
  231. // not valid Amount with current Balance. Discard L2Tx,
  232. // and update Info parameter of the tx, and add it to
  233. // the discardedTxs array
  234. l2Txs[i].Info = fmt.Sprintf("Tx not selected due to not enough Balance at the sender. "+
  235. "Current sender account Balance: %s, Amount+Fee: %s",
  236. balance.String(), feeAndAmount.String())
  237. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  238. continue
  239. }
  240. // check if Nonce is correct
  241. nonce := noncesMap[l2Txs[i].FromIdx]
  242. if l2Txs[i].Nonce == nonce {
  243. noncesMap[l2Txs[i].FromIdx]++
  244. } else {
  245. // not valid Nonce at tx. Discard L2Tx, and update Info
  246. // parameter of the tx, and add it to the discardedTxs
  247. // array
  248. l2Txs[i].Info = fmt.Sprintf("Tx not selected due to not current Nonce. "+
  249. "Tx.Nonce: %d, Account.Nonce: %d", l2Txs[i].Nonce, nonce)
  250. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  251. continue
  252. }
  253. // If tx.ToIdx>=256, tx.ToIdx should exist to localAccountsDB,
  254. // if so, tx is used. If tx.ToIdx==0, for an L2Tx will be the
  255. // case of TxToEthAddr or TxToBJJ, check if
  256. // tx.ToEthAddr/tx.ToBJJ exist in localAccountsDB, if yes tx is
  257. // used; if not, check if tx.ToEthAddr is in
  258. // AccountCreationAuthDB, if so, tx is used and L1CoordinatorTx
  259. // of CreateAccountAndDeposit is created. If tx.ToIdx==1, is a
  260. // Exit type and is used.
  261. if l2Txs[i].ToIdx == 0 { // ToEthAddr/ToBJJ case
  262. validL2Tx, l1CoordinatorTx, accAuth, err :=
  263. txsel.processTxToEthAddrBJJ(validTxs, selectionConfig,
  264. len(l1UserTxs), l1CoordinatorTxs, positionL1, l2Txs[i])
  265. if err != nil {
  266. log.Debugw("txsel.processTxToEthAddrBJJ", "err", err)
  267. // Discard L2Tx, and update Info parameter of
  268. // the tx, and add it to the discardedTxs array
  269. l2Txs[i].Info = fmt.Sprintf("Tx not selected (in processTxToEthAddrBJJ) due to %s",
  270. err.Error())
  271. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  272. continue
  273. }
  274. if l1CoordinatorTx != nil {
  275. // If ToEthAddr == 0xff.. this means that we
  276. // are handling a TransferToBJJ, which doesn't
  277. // require an authorization because it doesn't
  278. // contain a valid ethereum address.
  279. // Otherwise only create the account if we have
  280. // the corresponding authorization
  281. if validL2Tx.ToEthAddr == common.FFAddr {
  282. accAuths = append(accAuths, common.EmptyEthSignature)
  283. l1CoordinatorTxs = append(l1CoordinatorTxs, *l1CoordinatorTx)
  284. positionL1++
  285. } else if accAuth != nil {
  286. accAuths = append(accAuths, accAuth.Signature)
  287. l1CoordinatorTxs = append(l1CoordinatorTxs, *l1CoordinatorTx)
  288. positionL1++
  289. }
  290. }
  291. if validL2Tx != nil {
  292. validTxs = append(validTxs, *validL2Tx)
  293. }
  294. } else if l2Txs[i].ToIdx >= common.IdxUserThreshold {
  295. receiverAcc, err := txsel.localAccountsDB.GetAccount(l2Txs[i].ToIdx)
  296. if err != nil {
  297. // tx not valid
  298. log.Debugw("invalid L2Tx: ToIdx not found in StateDB",
  299. "ToIdx", l2Txs[i].ToIdx)
  300. // Discard L2Tx, and update Info parameter of
  301. // the tx, and add it to the discardedTxs array
  302. l2Txs[i].Info = fmt.Sprintf("Tx not selected due to tx.ToIdx not found in StateDB. "+
  303. "ToIdx: %d", l2Txs[i].ToIdx)
  304. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  305. continue
  306. }
  307. if l2Txs[i].ToEthAddr != common.EmptyAddr {
  308. if l2Txs[i].ToEthAddr != receiverAcc.EthAddr {
  309. log.Debugw("invalid L2Tx: ToEthAddr does not correspond to the Account.EthAddr",
  310. "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr",
  311. l2Txs[i].ToEthAddr, "account.EthAddr", receiverAcc.EthAddr)
  312. // Discard L2Tx, and update Info
  313. // parameter of the tx, and add it to
  314. // the discardedTxs array
  315. l2Txs[i].Info = fmt.Sprintf("Tx not selected because ToEthAddr "+
  316. "does not correspond to the Account.EthAddr. "+
  317. "tx.ToIdx: %d, tx.ToEthAddr: %s, account.EthAddr: %s",
  318. l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, receiverAcc.EthAddr)
  319. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  320. continue
  321. }
  322. }
  323. if l2Txs[i].ToBJJ != common.EmptyBJJComp {
  324. if l2Txs[i].ToBJJ != receiverAcc.BJJ {
  325. log.Debugw("invalid L2Tx: ToBJJ does not correspond to the Account.BJJ",
  326. "ToIdx", l2Txs[i].ToIdx, "tx.ToEthAddr", l2Txs[i].ToBJJ,
  327. "account.BJJ", receiverAcc.BJJ)
  328. // Discard L2Tx, and update Info
  329. // parameter of the tx, and add it to
  330. // the discardedTxs array
  331. l2Txs[i].Info = fmt.Sprintf("Tx not selected because tx.ToBJJ "+
  332. "does not correspond to the Account.BJJ. "+
  333. "tx.ToIdx: %d, tx.ToEthAddr: %s, tx.ToBJJ: %s, account.BJJ: %s",
  334. l2Txs[i].ToIdx, l2Txs[i].ToEthAddr, l2Txs[i].ToBJJ, receiverAcc.BJJ)
  335. discardedL2Txs = append(discardedL2Txs, l2Txs[i])
  336. continue
  337. }
  338. }
  339. // Account found in the DB, include the l2Tx in the selection
  340. validTxs = append(validTxs, l2Txs[i])
  341. } else if l2Txs[i].ToIdx == common.Idx(1) {
  342. // valid txs (of Exit type)
  343. validTxs = append(validTxs, l2Txs[i])
  344. }
  345. }
  346. // Process L1CoordinatorTxs
  347. for i := 0; i < len(l1CoordinatorTxs); i++ {
  348. _, _, _, _, err := tp.ProcessL1Tx(nil, &l1CoordinatorTxs[i])
  349. if err != nil {
  350. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  351. }
  352. }
  353. // get CoordIdxsMap for the TokenIDs
  354. coordIdxsMap := make(map[common.TokenID]common.Idx)
  355. for i := 0; i < len(validTxs); i++ {
  356. // get TokenID from tx.Sender
  357. accSender, err := tp.StateDB().GetAccount(validTxs[i].FromIdx)
  358. if err != nil {
  359. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  360. }
  361. tokenID := accSender.TokenID
  362. coordIdx, err := txsel.getCoordIdx(tokenID)
  363. if err != nil {
  364. // if err is db.ErrNotFound, should not happen, as all
  365. // the validTxs.TokenID should have a CoordinatorIdx
  366. // created in the DB at this point
  367. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  368. }
  369. coordIdxsMap[tokenID] = coordIdx
  370. }
  371. var coordIdxs []common.Idx
  372. tp.AccumulatedFees = make(map[common.Idx]*big.Int)
  373. for _, idx := range coordIdxsMap {
  374. tp.AccumulatedFees[idx] = big.NewInt(0)
  375. coordIdxs = append(coordIdxs, idx)
  376. }
  377. // sort CoordIdxs
  378. sort.SliceStable(coordIdxs, func(i, j int) bool {
  379. return coordIdxs[i] < coordIdxs[j]
  380. })
  381. // get most profitable L2-tx
  382. maxL2Txs := int(selectionConfig.TxProcessorConfig.MaxTx) -
  383. len(l1UserTxs) - len(l1CoordinatorTxs)
  384. selectedL2Txs := validTxs
  385. if len(validTxs) > maxL2Txs {
  386. selectedL2Txs = selectedL2Txs[:maxL2Txs]
  387. }
  388. var finalL2Txs []common.PoolL2Tx
  389. for i := 0; i < len(selectedL2Txs); i++ {
  390. _, _, _, err = tp.ProcessL2Tx(coordIdxsMap, nil, nil, &selectedL2Txs[i])
  391. if err != nil {
  392. // the error can be due not valid tx data, or due other
  393. // cases (such as StateDB error). At this initial
  394. // version of the TxSelector, we discard the L2Tx and
  395. // log the error, assuming that this will be iterated
  396. // in a near future.
  397. log.Error(err)
  398. // Discard L2Tx, and update Info parameter of the tx,
  399. // and add it to the discardedTxs array
  400. selectedL2Txs[i].Info = fmt.Sprintf("Tx not selected (in ProcessL2Tx) due to %s", err.Error())
  401. discardedL2Txs = append(discardedL2Txs, selectedL2Txs[i])
  402. continue
  403. }
  404. finalL2Txs = append(finalL2Txs, selectedL2Txs[i])
  405. }
  406. // distribute the AccumulatedFees from the processed L2Txs into the
  407. // Coordinator Idxs
  408. for idx, accumulatedFee := range tp.AccumulatedFees {
  409. cmp := accumulatedFee.Cmp(big.NewInt(0))
  410. if cmp == 1 { // accumulatedFee>0
  411. // send the fee to the Idx of the Coordinator for the TokenID
  412. accCoord, err := txsel.localAccountsDB.GetAccount(idx)
  413. if err != nil {
  414. log.Errorw("Can not distribute accumulated fees to coordinator "+
  415. "account: No coord Idx to receive fee", "idx", idx)
  416. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  417. }
  418. accCoord.Balance = new(big.Int).Add(accCoord.Balance, accumulatedFee)
  419. _, err = txsel.localAccountsDB.UpdateAccount(idx, accCoord)
  420. if err != nil {
  421. log.Error(err)
  422. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  423. }
  424. }
  425. }
  426. err = tp.StateDB().MakeCheckpoint()
  427. if err != nil {
  428. return nil, nil, nil, nil, nil, nil, tracerr.Wrap(err)
  429. }
  430. metricSelectedL1CoordinatorTxs.Set(float64(len(l1CoordinatorTxs)))
  431. metricSelectedL1UserTxs.Set(float64(len(l1UserTxs)))
  432. metricSelectedL2Txs.Set(float64(len(finalL2Txs)))
  433. metricDiscardedL2Txs.Set(float64(len(discardedL2Txs)))
  434. return coordIdxs, accAuths, l1UserTxs, l1CoordinatorTxs, finalL2Txs, discardedL2Txs, nil
  435. }
  436. // processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where
  437. // ToIdx==0, which can be the tx type of ToEthAddr or ToBJJ. If the receiver
  438. // does not have an account yet, a new L1CoordinatorTx of type
  439. // CreateAccountDeposit (with 0 as DepositAmount) is created and added to the
  440. // l1CoordinatorTxs array, and then the PoolL2Tx is added into the validTxs
  441. // array.
  442. func (txsel *TxSelector) processTxToEthAddrBJJ(validTxs []common.PoolL2Tx,
  443. selectionConfig *SelectionConfig, nL1UserTxs int, l1CoordinatorTxs []common.L1Tx,
  444. positionL1 int, l2Tx common.PoolL2Tx) (*common.PoolL2Tx, *common.L1Tx,
  445. *common.AccountCreationAuth, error) {
  446. // if L2Tx needs a new L1CoordinatorTx of CreateAccount type, and a
  447. // previous L2Tx in the current process already created a
  448. // L1CoordinatorTx of this type, in the DB there still seem that needs
  449. // to create a new L1CoordinatorTx, but as is already created, the tx
  450. // is valid
  451. if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2Tx.TokenID, l2Tx.ToEthAddr, l2Tx.ToBJJ) {
  452. return &l2Tx, nil, nil, nil
  453. }
  454. var l1CoordinatorTx *common.L1Tx
  455. var accAuth *common.AccountCreationAuth
  456. if l2Tx.ToEthAddr != common.EmptyAddr && l2Tx.ToEthAddr != common.FFAddr {
  457. // case: ToEthAddr != 0x00 neither 0xff
  458. if l2Tx.ToBJJ != common.EmptyBJJComp {
  459. // case: ToBJJ!=0:
  460. // if idx exist for EthAddr&BJJ use it
  461. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr,
  462. l2Tx.ToBJJ, l2Tx.TokenID)
  463. if err == nil {
  464. // account for ToEthAddr&ToBJJ already exist,
  465. // there is no need to create a new one.
  466. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  467. return &l2Tx, nil, nil, nil
  468. }
  469. // if not, check if AccountCreationAuth exist for that
  470. // ToEthAddr
  471. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  472. if err != nil {
  473. // not found, l2Tx will not be added in the selection
  474. return nil, nil, nil,
  475. tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found "+
  476. "in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
  477. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex()))
  478. }
  479. if accAuth.BJJ != l2Tx.ToBJJ {
  480. // if AccountCreationAuth.BJJ is not the same
  481. // than in the tx, tx is not accepted
  482. return nil, nil, nil,
  483. tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in StateDB, "+
  484. "neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB. "+
  485. "ToIdx: %d, ToEthAddr: %s, ToBJJ: %s",
  486. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex(), l2Tx.ToBJJ.String()))
  487. }
  488. } else {
  489. // case: ToBJJ==0:
  490. // if idx exist for EthAddr use it
  491. _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2Tx.ToEthAddr, l2Tx.TokenID)
  492. if err == nil {
  493. // account for ToEthAddr already exist,
  494. // there is no need to create a new one.
  495. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  496. return &l2Tx, nil, nil, nil
  497. }
  498. // if not, check if AccountCreationAuth exist for that ToEthAddr
  499. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  500. if err != nil {
  501. // not found, l2Tx will not be added in the selection
  502. return nil, nil, nil,
  503. tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in "+
  504. "StateDB, neither ToEthAddr found in "+
  505. "AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
  506. l2Tx.ToIdx, l2Tx.ToEthAddr))
  507. }
  508. }
  509. // create L1CoordinatorTx for the accountCreation
  510. l1CoordinatorTx = &common.L1Tx{
  511. Position: positionL1,
  512. UserOrigin: false,
  513. FromEthAddr: accAuth.EthAddr,
  514. FromBJJ: accAuth.BJJ,
  515. TokenID: l2Tx.TokenID,
  516. Amount: big.NewInt(0),
  517. DepositAmount: big.NewInt(0),
  518. Type: common.TxTypeCreateAccountDeposit,
  519. }
  520. } else if l2Tx.ToEthAddr == common.FFAddr && l2Tx.ToBJJ != common.EmptyBJJComp {
  521. // if idx exist for EthAddr&BJJ use it
  522. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr, l2Tx.ToBJJ,
  523. l2Tx.TokenID)
  524. if err == nil {
  525. // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
  526. // there is no need to create a new one.
  527. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  528. return &l2Tx, nil, nil, nil
  529. }
  530. // if idx don't exist for EthAddr&BJJ, coordinator can create a
  531. // new account without L1Authorization, as ToEthAddr==0xff
  532. // create L1CoordinatorTx for the accountCreation
  533. l1CoordinatorTx = &common.L1Tx{
  534. Position: positionL1,
  535. UserOrigin: false,
  536. FromEthAddr: l2Tx.ToEthAddr,
  537. FromBJJ: l2Tx.ToBJJ,
  538. TokenID: l2Tx.TokenID,
  539. Amount: big.NewInt(0),
  540. DepositAmount: big.NewInt(0),
  541. Type: common.TxTypeCreateAccountDeposit,
  542. }
  543. }
  544. if len(l1CoordinatorTxs) >= int(selectionConfig.MaxL1UserTxs)-nL1UserTxs {
  545. // L2Tx discarded
  546. return nil, nil, nil, tracerr.Wrap(fmt.Errorf("L2Tx discarded due to no available slots " +
  547. "for L1CoordinatorTx to create a new account for receiver of L2Tx"))
  548. }
  549. return &l2Tx, l1CoordinatorTx, accAuth, nil
  550. }
  551. func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx, tokenID common.TokenID,
  552. addr ethCommon.Address, bjj babyjub.PublicKeyComp) bool {
  553. for i := 0; i < len(l1CoordinatorTxs); i++ {
  554. if l1CoordinatorTxs[i].FromEthAddr == addr &&
  555. l1CoordinatorTxs[i].TokenID == tokenID &&
  556. l1CoordinatorTxs[i].FromBJJ == bjj {
  557. return true
  558. }
  559. }
  560. return false
  561. }
  562. // getL2Profitable returns the profitable selection of L2Txssorted by Nonce
  563. func (txsel *TxSelector) getL2Profitable(l2Txs []common.PoolL2Tx, max uint32) ([]common.PoolL2Tx,
  564. []common.PoolL2Tx) {
  565. // First sort by nonce so that txs from the same account are sorted so
  566. // that they could be applied in succession.
  567. sort.Slice(l2Txs, func(i, j int) bool {
  568. return l2Txs[i].Nonce < l2Txs[j].Nonce
  569. })
  570. // Sort by absolute fee with SliceStable, so that txs with same
  571. // AbsoluteFee are not rearranged and nonce order is kept in such case
  572. sort.SliceStable(l2Txs, func(i, j int) bool {
  573. return l2Txs[i].AbsoluteFee > l2Txs[j].AbsoluteFee
  574. })
  575. discardedL2Txs := []common.PoolL2Tx{}
  576. if len(l2Txs) > int(max) {
  577. discardedL2Txs = l2Txs[max:]
  578. l2Txs = l2Txs[:max]
  579. }
  580. // sort l2Txs by Nonce. This can be done in many different ways, what
  581. // is needed is to output the l2Txs where the Nonce of l2Txs for each
  582. // Account is sorted, but the l2Txs can not be grouped by sender Account
  583. // neither by Fee. This is because later on the Nonces will need to be
  584. // sequential for the zkproof generation.
  585. sort.Slice(l2Txs, func(i, j int) bool {
  586. return l2Txs[i].Nonce < l2Txs[j].Nonce
  587. })
  588. return l2Txs, discardedL2Txs
  589. }