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.

393 lines
13 KiB

  1. package txselector
  2. // current: very simple version of TxSelector
  3. import (
  4. "bytes"
  5. "fmt"
  6. "math/big"
  7. "sort"
  8. ethCommon "github.com/ethereum/go-ethereum/common"
  9. "github.com/hermeznetwork/hermez-node/common"
  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/tracerr"
  14. "github.com/iden3/go-iden3-crypto/babyjub"
  15. "github.com/iden3/go-merkletree/db/pebble"
  16. )
  17. const (
  18. // PathCoordIdxsDB defines the path of the key-value db where the
  19. // CoordIdxs will be stored
  20. PathCoordIdxsDB = "/coordidxs"
  21. )
  22. // txs implements the interface Sort for an array of Tx
  23. type txs []common.PoolL2Tx
  24. func (t txs) Len() int {
  25. return len(t)
  26. }
  27. func (t txs) Swap(i, j int) {
  28. t[i], t[j] = t[j], t[i]
  29. }
  30. func (t txs) Less(i, j int) bool {
  31. return t[i].AbsoluteFee > t[j].AbsoluteFee
  32. }
  33. // CoordAccount contains the data of the Coordinator account, that will be used
  34. // to create new transactions of CreateAccountDeposit type to add new TokenID
  35. // accounts for the Coordinator to receive the fees.
  36. type CoordAccount struct {
  37. Addr ethCommon.Address
  38. BJJ *babyjub.PublicKey
  39. AccountCreationAuth []byte
  40. }
  41. // SelectionConfig contains the parameters of configuration of the selection of
  42. // transactions for the next batch
  43. type SelectionConfig struct {
  44. // MaxL1UserTxs is the maximum L1-user-tx for a batch
  45. MaxL1UserTxs uint64
  46. // MaxL1CoordinatorTxs is the maximum L1-coordinator-tx for a batch
  47. MaxL1CoordinatorTxs uint64
  48. // ProcessTxsConfig contains the config for ProcessTxs
  49. ProcessTxsConfig statedb.ProcessTxsConfig
  50. }
  51. // TxSelector implements all the functionalities to select the txs for the next
  52. // batch
  53. type TxSelector struct {
  54. l2db *l2db.L2DB
  55. localAccountsDB *statedb.LocalStateDB
  56. coordAccount *CoordAccount
  57. coordIdxsDB *pebble.PebbleStorage
  58. }
  59. // NewTxSelector returns a *TxSelector
  60. func NewTxSelector(coordAccount *CoordAccount, dbpath string,
  61. synchronizerStateDB *statedb.StateDB, l2 *l2db.L2DB) (*TxSelector, error) {
  62. localAccountsDB, err := statedb.NewLocalStateDB(dbpath,
  63. synchronizerStateDB, statedb.TypeTxSelector, 0) // without merkletree
  64. if err != nil {
  65. return nil, tracerr.Wrap(err)
  66. }
  67. coordIdxsDB, err := pebble.NewPebbleStorage(dbpath+PathCoordIdxsDB, false)
  68. if err != nil {
  69. return nil, tracerr.Wrap(err)
  70. }
  71. return &TxSelector{
  72. l2db: l2,
  73. localAccountsDB: localAccountsDB,
  74. coordAccount: coordAccount,
  75. coordIdxsDB: coordIdxsDB,
  76. }, nil
  77. }
  78. // LocalAccountsDB returns the LocalStateDB of the TxSelector
  79. func (txsel *TxSelector) LocalAccountsDB() *statedb.LocalStateDB {
  80. return txsel.localAccountsDB
  81. }
  82. // Reset tells the TxSelector to get it's internal AccountsDB
  83. // from the required `batchNum`
  84. func (txsel *TxSelector) Reset(batchNum common.BatchNum) error {
  85. err := txsel.localAccountsDB.Reset(batchNum, true)
  86. if err != nil {
  87. return tracerr.Wrap(err)
  88. }
  89. return nil
  90. }
  91. // AddCoordIdxs stores the given TokenID with the correspondent Idx to the
  92. // CoordIdxsDB
  93. func (txsel *TxSelector) AddCoordIdxs(idxs map[common.TokenID]common.Idx) error {
  94. tx, err := txsel.coordIdxsDB.NewTx()
  95. if err != nil {
  96. return tracerr.Wrap(err)
  97. }
  98. for tokenID, idx := range idxs {
  99. idxBytes, err := idx.Bytes()
  100. if err != nil {
  101. return tracerr.Wrap(err)
  102. }
  103. err = tx.Put(tokenID.Bytes(), idxBytes[:])
  104. if err != nil {
  105. return tracerr.Wrap(err)
  106. }
  107. }
  108. if err := tx.Commit(); err != nil {
  109. return tracerr.Wrap(err)
  110. }
  111. return nil
  112. }
  113. // GetCoordIdxs returns a map with the stored TokenID with the correspondent
  114. // Coordinator Idx
  115. func (txsel *TxSelector) GetCoordIdxs() (map[common.TokenID]common.Idx, error) {
  116. r := make(map[common.TokenID]common.Idx)
  117. err := txsel.coordIdxsDB.Iterate(func(tokenIDBytes []byte, idxBytes []byte) (bool, error) {
  118. idx, err := common.IdxFromBytes(idxBytes)
  119. if err != nil {
  120. return false, tracerr.Wrap(err)
  121. }
  122. tokenID, err := common.TokenIDFromBytes(tokenIDBytes)
  123. if err != nil {
  124. return false, tracerr.Wrap(err)
  125. }
  126. r[tokenID] = idx
  127. return true, nil
  128. })
  129. return r, tracerr.Wrap(err)
  130. }
  131. // GetL2TxSelection returns the L1CoordinatorTxs and a selection of the L2Txs
  132. // for the next batch, from the L2DB pool
  133. func (txsel *TxSelector) GetL2TxSelection(selectionConfig *SelectionConfig,
  134. batchNum common.BatchNum) ([]common.Idx, []common.L1Tx, []common.PoolL2Tx, error) {
  135. coordIdxs, _, l1CoordinatorTxs, l2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, batchNum,
  136. []common.L1Tx{})
  137. return coordIdxs, l1CoordinatorTxs, l2Txs, tracerr.Wrap(err)
  138. }
  139. // GetL1L2TxSelection returns the selection of L1 + L2 txs
  140. func (txsel *TxSelector) GetL1L2TxSelection(selectionConfig *SelectionConfig,
  141. batchNum common.BatchNum, l1Txs []common.L1Tx) ([]common.Idx, []common.L1Tx, []common.L1Tx,
  142. []common.PoolL2Tx, error) {
  143. // apply l1-user-tx to localAccountDB
  144. // create new leaves
  145. // update balances
  146. // update nonces
  147. // get existing CoordIdxs
  148. coordIdxsMap, err := txsel.GetCoordIdxs()
  149. if err != nil {
  150. return nil, nil, nil, nil, tracerr.Wrap(err)
  151. }
  152. var coordIdxs []common.Idx
  153. for tokenID := range coordIdxsMap {
  154. coordIdxs = append(coordIdxs, coordIdxsMap[tokenID])
  155. }
  156. // get pending l2-tx from tx-pool
  157. l2TxsRaw, err := txsel.l2db.GetPendingTxs() // (batchID)
  158. if err != nil {
  159. return nil, nil, nil, nil, tracerr.Wrap(err)
  160. }
  161. var validTxs txs
  162. var l1CoordinatorTxs []common.L1Tx
  163. positionL1 := len(l1Txs)
  164. for i := 0; i < len(l2TxsRaw); i++ {
  165. // If tx.ToIdx>=256, tx.ToIdx should exist to localAccountsDB,
  166. // if so, tx is used. If tx.ToIdx==0, for an L2Tx will be the
  167. // case of TxToEthAddr or TxToBJJ, check if
  168. // tx.ToEthAddr/tx.ToBJJ exist in localAccountsDB, if yes tx is
  169. // used; if not, check if tx.ToEthAddr is in
  170. // AccountCreationAuthDB, if so, tx is used and L1CoordinatorTx
  171. // of CreateAccountAndDeposit is created. If tx.ToIdx==1, is a
  172. // Exit type and is used.
  173. if l2TxsRaw[i].ToIdx == 0 { // ToEthAddr/ToBJJ case
  174. validTxs, l1CoordinatorTxs, positionL1, err =
  175. txsel.processTxToEthAddrBJJ(validTxs, l1CoordinatorTxs,
  176. positionL1, l2TxsRaw[i])
  177. if err != nil {
  178. log.Debug(err)
  179. }
  180. } else if l2TxsRaw[i].ToIdx >= common.IdxUserThreshold {
  181. _, err = txsel.localAccountsDB.GetAccount(l2TxsRaw[i].ToIdx)
  182. if err != nil {
  183. // tx not valid
  184. log.Debugw("invalid L2Tx: ToIdx not found in StateDB",
  185. "ToIdx", l2TxsRaw[i].ToIdx)
  186. continue
  187. }
  188. // TODO if EthAddr!=0 or BJJ!=0, check that ToIdxAccount.EthAddr or BJJ
  189. // Account found in the DB, include the l2Tx in the selection
  190. validTxs = append(validTxs, l2TxsRaw[i])
  191. } else if l2TxsRaw[i].ToIdx == common.Idx(1) {
  192. // valid txs (of Exit type)
  193. validTxs = append(validTxs, l2TxsRaw[i])
  194. }
  195. }
  196. // get most profitable L2-tx
  197. maxL2Txs := selectionConfig.ProcessTxsConfig.MaxTx - uint32(len(l1CoordinatorTxs)) // - len(l1UserTxs) // TODO if there are L1UserTxs take them in to account
  198. l2Txs := txsel.getL2Profitable(validTxs, maxL2Txs)
  199. //nolint:gomnd
  200. ptc := statedb.ProcessTxsConfig{ // TODO TMP
  201. NLevels: 32,
  202. MaxFeeTx: 64,
  203. MaxTx: 512,
  204. MaxL1Tx: 64,
  205. }
  206. // process the txs in the local AccountsDB
  207. _, err = txsel.localAccountsDB.ProcessTxs(ptc, coordIdxs, l1Txs, l1CoordinatorTxs, l2Txs)
  208. if err != nil {
  209. return nil, nil, nil, nil, tracerr.Wrap(err)
  210. }
  211. err = txsel.localAccountsDB.MakeCheckpoint()
  212. if err != nil {
  213. return nil, nil, nil, nil, tracerr.Wrap(err)
  214. }
  215. return nil, l1Txs, l1CoordinatorTxs, l2Txs, nil
  216. }
  217. // processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where
  218. // ToIdx==0, which can be the tx type of ToEthAddr or ToBJJ. If the receiver
  219. // does not have an account yet, a new L1CoordinatorTx of type
  220. // CreateAccountDeposit (with 0 as DepositAmount) is created and added to the
  221. // l1CoordinatorTxs array, and then the PoolL2Tx is added into the validTxs
  222. // array.
  223. func (txsel *TxSelector) processTxToEthAddrBJJ(validTxs txs, l1CoordinatorTxs []common.L1Tx,
  224. positionL1 int, l2Tx common.PoolL2Tx) (txs, []common.L1Tx, int, error) {
  225. // if L2Tx needs a new L1CoordinatorTx of CreateAccount type, and a
  226. // previous L2Tx in the current process already created a
  227. // L1CoordinatorTx of this type, in the DB there still seem that needs
  228. // to create a new L1CoordinatorTx, but as is already created, the tx
  229. // is valid
  230. if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2Tx.ToEthAddr, l2Tx.ToBJJ) {
  231. validTxs = append(validTxs, l2Tx)
  232. return validTxs, l1CoordinatorTxs, positionL1, nil
  233. }
  234. if !bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.EmptyAddr.Bytes()) &&
  235. !bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.FFAddr.Bytes()) {
  236. // case: ToEthAddr != 0x00 neither 0xff
  237. var accAuth *common.AccountCreationAuth
  238. if l2Tx.ToBJJ != common.EmptyBJJComp {
  239. // case: ToBJJ!=0:
  240. // if idx exist for EthAddr&BJJ use it
  241. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr,
  242. l2Tx.ToBJJ, l2Tx.TokenID)
  243. if err == nil {
  244. // account for ToEthAddr&ToBJJ already exist,
  245. // there is no need to create a new one.
  246. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  247. validTxs = append(validTxs, l2Tx)
  248. return validTxs, l1CoordinatorTxs, positionL1, nil
  249. }
  250. // if not, check if AccountCreationAuth exist for that
  251. // ToEthAddr
  252. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  253. if err != nil {
  254. // not found, l2Tx will not be added in the selection
  255. return validTxs, l1CoordinatorTxs, positionL1, tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
  256. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex()))
  257. }
  258. if accAuth.BJJ != l2Tx.ToBJJ {
  259. // if AccountCreationAuth.BJJ is not the same
  260. // than in the tx, tx is not accepted
  261. return validTxs, l1CoordinatorTxs, positionL1, tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr & ToBJJ found in AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s, ToBJJ: %s",
  262. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex(), l2Tx.ToBJJ.String()))
  263. }
  264. validTxs = append(validTxs, l2Tx)
  265. } else {
  266. // case: ToBJJ==0:
  267. // if idx exist for EthAddr use it
  268. _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2Tx.ToEthAddr, l2Tx.TokenID)
  269. if err == nil {
  270. // account for ToEthAddr already exist,
  271. // there is no need to create a new one.
  272. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  273. validTxs = append(validTxs, l2Tx)
  274. return validTxs, l1CoordinatorTxs, positionL1, nil
  275. }
  276. // if not, check if AccountCreationAuth exist for that ToEthAddr
  277. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  278. if err != nil {
  279. // not found, l2Tx will not be added in the selection
  280. return validTxs, l1CoordinatorTxs, positionL1, tracerr.Wrap(fmt.Errorf("invalid L2Tx: ToIdx not found in StateDB, neither ToEthAddr found in AccountCreationAuths L2DB. ToIdx: %d, ToEthAddr: %s",
  281. l2Tx.ToIdx, l2Tx.ToEthAddr))
  282. }
  283. validTxs = append(validTxs, l2Tx)
  284. }
  285. // create L1CoordinatorTx for the accountCreation
  286. l1CoordinatorTx := common.L1Tx{
  287. Position: positionL1,
  288. UserOrigin: false,
  289. FromEthAddr: accAuth.EthAddr,
  290. FromBJJ: accAuth.BJJ,
  291. TokenID: l2Tx.TokenID,
  292. DepositAmount: big.NewInt(0),
  293. Type: common.TxTypeCreateAccountDeposit,
  294. }
  295. positionL1++
  296. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  297. } else if bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.FFAddr.Bytes()) && l2Tx.ToBJJ != common.EmptyBJJComp {
  298. // if idx exist for EthAddr&BJJ use it
  299. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr, l2Tx.ToBJJ,
  300. l2Tx.TokenID)
  301. if err == nil {
  302. // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
  303. // there is no need to create a new one.
  304. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  305. validTxs = append(validTxs, l2Tx)
  306. return validTxs, l1CoordinatorTxs, positionL1, nil
  307. }
  308. // if idx don't exist for EthAddr&BJJ,
  309. // coordinator can create a new account without
  310. // L1Authorization, as ToEthAddr==0xff
  311. // create L1CoordinatorTx for the accountCreation
  312. l1CoordinatorTx := common.L1Tx{
  313. Position: positionL1,
  314. UserOrigin: false,
  315. FromEthAddr: l2Tx.ToEthAddr,
  316. FromBJJ: l2Tx.ToBJJ,
  317. TokenID: l2Tx.TokenID,
  318. DepositAmount: big.NewInt(0),
  319. Type: common.TxTypeCreateAccountDeposit,
  320. }
  321. positionL1++
  322. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  323. }
  324. return validTxs, l1CoordinatorTxs, positionL1, nil
  325. }
  326. func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx,
  327. addr ethCommon.Address, bjj babyjub.PublicKeyComp) bool {
  328. for i := 0; i < len(l1CoordinatorTxs); i++ {
  329. if bytes.Equal(l1CoordinatorTxs[i].FromEthAddr.Bytes(), addr.Bytes()) {
  330. if bjj == common.EmptyBJJComp {
  331. return true
  332. }
  333. if l1CoordinatorTxs[i].FromBJJ == bjj {
  334. return true
  335. }
  336. }
  337. }
  338. return false
  339. }
  340. // getL2Profitable returns the profitable selection of L2Txssorted by Nonce
  341. func (txsel *TxSelector) getL2Profitable(txs txs, max uint32) txs {
  342. sort.Sort(txs)
  343. if len(txs) < int(max) {
  344. return txs
  345. }
  346. txs = txs[:max]
  347. // sort l2Txs by Nonce. This can be done in many different ways, what
  348. // is needed is to output the txs where the Nonce of txs for each
  349. // Account is sorted, but the txs can not be grouped by sender Account
  350. // neither by Fee. This is because later on the Nonces will need to be
  351. // sequential for the zkproof generation.
  352. sort.SliceStable(txs, func(i, j int) bool {
  353. return txs[i].Nonce < txs[j].Nonce
  354. })
  355. return txs
  356. }