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.

270 lines
9.9 KiB

  1. package txselector
  2. // current: very simple version of TxSelector
  3. import (
  4. "bytes"
  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/l2db"
  10. "github.com/hermeznetwork/hermez-node/db/statedb"
  11. "github.com/hermeznetwork/hermez-node/log"
  12. "github.com/hermeznetwork/tracerr"
  13. "github.com/iden3/go-iden3-crypto/babyjub"
  14. )
  15. // txs implements the interface Sort for an array of Tx
  16. type txs []common.PoolL2Tx
  17. func (t txs) Len() int {
  18. return len(t)
  19. }
  20. func (t txs) Swap(i, j int) {
  21. t[i], t[j] = t[j], t[i]
  22. }
  23. func (t txs) Less(i, j int) bool {
  24. return t[i].AbsoluteFee > t[j].AbsoluteFee
  25. }
  26. // TxSelector implements all the functionalities to select the txs for the next batch
  27. type TxSelector struct {
  28. // MaxL1UserTxs is the maximum L1-user-tx for a batch
  29. MaxL1UserTxs uint64
  30. // MaxL1OperatorTxs is the maximum L1-operator-tx for a batch
  31. MaxL1OperatorTxs uint64
  32. // MaxTxs is the maximum txs for a batch
  33. MaxTxs uint64
  34. l2db *l2db.L2DB
  35. localAccountsDB *statedb.LocalStateDB
  36. }
  37. // NewTxSelector returns a *TxSelector
  38. func NewTxSelector(dbpath string, synchronizerStateDB *statedb.StateDB, l2 *l2db.L2DB, maxL1UserTxs, maxL1OperatorTxs, maxTxs uint64) (*TxSelector, error) {
  39. localAccountsDB, err := statedb.NewLocalStateDB(dbpath, synchronizerStateDB, statedb.TypeTxSelector, 0) // without merkletree
  40. if err != nil {
  41. return nil, tracerr.Wrap(err)
  42. }
  43. return &TxSelector{
  44. MaxL1UserTxs: maxL1UserTxs,
  45. MaxL1OperatorTxs: maxL1OperatorTxs,
  46. MaxTxs: maxTxs,
  47. l2db: l2,
  48. localAccountsDB: localAccountsDB,
  49. }, nil
  50. }
  51. // Reset tells the TxSelector to get it's internal AccountsDB
  52. // from the required `batchNum`
  53. func (txsel *TxSelector) Reset(batchNum common.BatchNum) error {
  54. err := txsel.localAccountsDB.Reset(batchNum, true)
  55. if err != nil {
  56. return tracerr.Wrap(err)
  57. }
  58. return nil
  59. }
  60. // GetL2TxSelection returns the L1CoordinatorTxs and a selection of the L2Txs
  61. // for the next batch, from the L2DB pool
  62. func (txsel *TxSelector) GetL2TxSelection(coordIdxs []common.Idx, batchNum common.BatchNum) ([]common.L1Tx, []common.PoolL2Tx, error) {
  63. _, l1CoordinatorTxs, l2Txs, err := txsel.GetL1L2TxSelection(coordIdxs, batchNum, []common.L1Tx{})
  64. return l1CoordinatorTxs, l2Txs, tracerr.Wrap(err)
  65. }
  66. // GetL1L2TxSelection returns the selection of L1 + L2 txs
  67. func (txsel *TxSelector) GetL1L2TxSelection(coordIdxs []common.Idx, batchNum common.BatchNum, l1Txs []common.L1Tx) ([]common.L1Tx, []common.L1Tx, []common.PoolL2Tx, error) {
  68. // apply l1-user-tx to localAccountDB
  69. // create new leaves
  70. // update balances
  71. // update nonces
  72. // get pending l2-tx from tx-pool
  73. l2TxsRaw, err := txsel.l2db.GetPendingTxs() // (batchID)
  74. if err != nil {
  75. return nil, nil, nil, tracerr.Wrap(err)
  76. }
  77. var validTxs txs
  78. var l1CoordinatorTxs []common.L1Tx
  79. positionL1 := len(l1Txs)
  80. // if tx.ToIdx>=256, tx.ToIdx should exist to localAccountsDB, if so,
  81. // tx is used. if tx.ToIdx==0, check if tx.ToEthAddr/tx.ToBJJ exist in
  82. // localAccountsDB, if yes tx is used; if not, check if tx.ToEthAddr is
  83. // in AccountCreationAuthDB, if so, tx is used and L1CoordinatorTx of
  84. // CreateAccountAndDeposit is created.
  85. for i := 0; i < len(l2TxsRaw); i++ {
  86. if l2TxsRaw[i].ToIdx == 0 {
  87. if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ) {
  88. // if L2Tx needs a new L1CoordinatorTx of CreateAccount type,
  89. // and a previous L2Tx in the current process already created
  90. // a L1CoordinatorTx of this type, in the DB there still seem
  91. // that needs to create a new L1CoordinatorTx, but as is already
  92. // created, the tx is valid
  93. validTxs = append(validTxs, l2TxsRaw[i])
  94. continue
  95. }
  96. if !bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.EmptyAddr.Bytes()) &&
  97. !bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.FFAddr.Bytes()) {
  98. // case: ToEthAddr != 0x00 neither 0xff
  99. var accAuth *common.AccountCreationAuth
  100. if l2TxsRaw[i].ToBJJ != nil {
  101. // case: ToBJJ!=0:
  102. // if idx exist for EthAddr&BJJ use it
  103. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ, l2TxsRaw[i].TokenID)
  104. if err == nil {
  105. // account for ToEthAddr&ToBJJ already exist,
  106. // there is no need to create a new one.
  107. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  108. validTxs = append(validTxs, l2TxsRaw[i])
  109. continue
  110. }
  111. // if not, check if AccountCreationAuth exist for that ToEthAddr&BJJ
  112. // accAuth, err = txsel.l2db.GetAccountCreationAuthBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ)
  113. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2TxsRaw[i].ToEthAddr)
  114. if err != nil {
  115. // not found, l2Tx will not be added in the selection
  116. log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr)
  117. continue
  118. }
  119. if accAuth.BJJ != l2TxsRaw[i].ToBJJ {
  120. // if AccountCreationAuth.BJJ is not the same than in the tx, tx is not accepted
  121. log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr, "ToBJJ", l2TxsRaw[i].ToBJJ)
  122. continue
  123. }
  124. validTxs = append(validTxs, l2TxsRaw[i])
  125. } else {
  126. // case: ToBJJ==0:
  127. // if idx exist for EthAddr use it
  128. _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].TokenID)
  129. if err == nil {
  130. // account for ToEthAddr already exist,
  131. // there is no need to create a new one.
  132. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  133. validTxs = append(validTxs, l2TxsRaw[i])
  134. continue
  135. }
  136. // if not, check if AccountCreationAuth exist for that ToEthAddr
  137. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2TxsRaw[i].ToEthAddr)
  138. if err != nil {
  139. // not found, l2Tx will not be added in the selection
  140. log.Debugw("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB", "ToIdx", l2TxsRaw[i].ToIdx, "ToEthAddr", l2TxsRaw[i].ToEthAddr)
  141. continue
  142. }
  143. validTxs = append(validTxs, l2TxsRaw[i])
  144. }
  145. // create L1CoordinatorTx for the accountCreation
  146. l1CoordinatorTx := common.L1Tx{
  147. Position: positionL1,
  148. UserOrigin: false,
  149. FromEthAddr: accAuth.EthAddr,
  150. FromBJJ: accAuth.BJJ,
  151. TokenID: l2TxsRaw[i].TokenID,
  152. DepositAmount: big.NewInt(0),
  153. Type: common.TxTypeCreateAccountDeposit,
  154. }
  155. positionL1++
  156. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  157. } else if bytes.Equal(l2TxsRaw[i].ToEthAddr.Bytes(), common.FFAddr.Bytes()) && l2TxsRaw[i].ToBJJ != nil {
  158. // if idx exist for EthAddr&BJJ use it
  159. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2TxsRaw[i].ToEthAddr, l2TxsRaw[i].ToBJJ, l2TxsRaw[i].TokenID)
  160. if err == nil {
  161. // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
  162. // there is no need to create a new one.
  163. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  164. validTxs = append(validTxs, l2TxsRaw[i])
  165. continue
  166. }
  167. // if idx don't exist for EthAddr&BJJ,
  168. // coordinator can create a new account without
  169. // L1Authorization, as ToEthAddr==0xff
  170. // create L1CoordinatorTx for the accountCreation
  171. l1CoordinatorTx := common.L1Tx{
  172. Position: positionL1,
  173. UserOrigin: false,
  174. FromEthAddr: l2TxsRaw[i].ToEthAddr,
  175. FromBJJ: l2TxsRaw[i].ToBJJ,
  176. TokenID: l2TxsRaw[i].TokenID,
  177. DepositAmount: big.NewInt(0),
  178. Type: common.TxTypeCreateAccountDeposit,
  179. }
  180. positionL1++
  181. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  182. }
  183. } else if l2TxsRaw[i].ToIdx >= common.IdxUserThreshold {
  184. _, err = txsel.localAccountsDB.GetAccount(l2TxsRaw[i].ToIdx)
  185. if err != nil {
  186. // tx not valid
  187. log.Debugw("invalid L2Tx: ToIdx not found in StateDB", "ToIdx", l2TxsRaw[i].ToIdx)
  188. continue
  189. }
  190. // Account found in the DB, include the l2Tx in the selection
  191. validTxs = append(validTxs, l2TxsRaw[i])
  192. } else if l2TxsRaw[i].ToIdx == common.Idx(1) {
  193. // valid txs (of Exit type)
  194. validTxs = append(validTxs, l2TxsRaw[i])
  195. }
  196. }
  197. // get most profitable L2-tx
  198. maxL2Txs := txsel.MaxTxs - uint64(len(l1CoordinatorTxs)) // - len(l1UserTxs) // TODO if there are L1UserTxs take them in to account
  199. l2Txs := txsel.getL2Profitable(validTxs, maxL2Txs)
  200. //nolint:gomnd
  201. ptc := statedb.ProcessTxsConfig{ // TODO TMP
  202. NLevels: 32,
  203. MaxFeeTx: 64,
  204. MaxTx: 512,
  205. MaxL1Tx: 64,
  206. }
  207. // process the txs in the local AccountsDB
  208. _, err = txsel.localAccountsDB.ProcessTxs(ptc, coordIdxs, l1Txs, l1CoordinatorTxs, l2Txs)
  209. if err != nil {
  210. return nil, nil, nil, tracerr.Wrap(err)
  211. }
  212. err = txsel.localAccountsDB.MakeCheckpoint()
  213. if err != nil {
  214. return nil, nil, nil, tracerr.Wrap(err)
  215. }
  216. return l1Txs, l1CoordinatorTxs, l2Txs, nil
  217. }
  218. func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx, addr ethCommon.Address, bjj *babyjub.PublicKey) bool {
  219. for i := 0; i < len(l1CoordinatorTxs); i++ {
  220. if bytes.Equal(l1CoordinatorTxs[i].FromEthAddr.Bytes(), addr.Bytes()) {
  221. if bjj == nil {
  222. return true
  223. }
  224. if l1CoordinatorTxs[i].FromBJJ == bjj {
  225. return true
  226. }
  227. }
  228. }
  229. return false
  230. }
  231. // getL2Profitable returns the profitable selection of L2Txssorted by Nonce
  232. func (txsel *TxSelector) getL2Profitable(txs txs, max uint64) txs {
  233. sort.Sort(txs)
  234. if len(txs) < int(max) {
  235. return txs
  236. }
  237. txs = txs[:max]
  238. // sort l2Txs by Nonce. This can be done in many different ways, what
  239. // is needed is to output the txs where the Nonce of txs for each
  240. // Account is sorted, but the txs can not be grouped by sender Account
  241. // neither by Fee. This is because later on the Nonces will need to be
  242. // sequential for the zkproof generation.
  243. sort.SliceStable(txs, func(i, j int) bool {
  244. return txs[i].Nonce < txs[j].Nonce
  245. })
  246. return txs
  247. }