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.

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