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.

398 lines
14 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, [][]byte, []common.L1Tx, []common.PoolL2Tx, error) {
  135. coordIdxs, auths, _, l1CoordinatorTxs, l2Txs, err := txsel.GetL1L2TxSelection(selectionConfig, batchNum,
  136. []common.L1Tx{})
  137. return coordIdxs, auths, 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, [][]byte, []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, 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, 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, nil, tracerr.Wrap(err)
  210. }
  211. err = txsel.localAccountsDB.MakeCheckpoint()
  212. if err != nil {
  213. return nil, nil, nil, nil, nil, tracerr.Wrap(err)
  214. }
  215. // TODO
  216. auths := make([][]byte, len(l1CoordinatorTxs))
  217. for i := range auths {
  218. auths[i] = make([]byte, 65)
  219. }
  220. return nil, auths, l1Txs, l1CoordinatorTxs, l2Txs, nil
  221. }
  222. // processTxsToEthAddrBJJ process the common.PoolL2Tx in the case where
  223. // ToIdx==0, which can be the tx type of ToEthAddr or ToBJJ. If the receiver
  224. // does not have an account yet, a new L1CoordinatorTx of type
  225. // CreateAccountDeposit (with 0 as DepositAmount) is created and added to the
  226. // l1CoordinatorTxs array, and then the PoolL2Tx is added into the validTxs
  227. // array.
  228. func (txsel *TxSelector) processTxToEthAddrBJJ(validTxs txs, l1CoordinatorTxs []common.L1Tx,
  229. positionL1 int, l2Tx common.PoolL2Tx) (txs, []common.L1Tx, int, error) {
  230. // if L2Tx needs a new L1CoordinatorTx of CreateAccount type, and a
  231. // previous L2Tx in the current process already created a
  232. // L1CoordinatorTx of this type, in the DB there still seem that needs
  233. // to create a new L1CoordinatorTx, but as is already created, the tx
  234. // is valid
  235. if checkAlreadyPendingToCreate(l1CoordinatorTxs, l2Tx.ToEthAddr, l2Tx.ToBJJ) {
  236. validTxs = append(validTxs, l2Tx)
  237. return validTxs, l1CoordinatorTxs, positionL1, nil
  238. }
  239. if !bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.EmptyAddr.Bytes()) &&
  240. !bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.FFAddr.Bytes()) {
  241. // case: ToEthAddr != 0x00 neither 0xff
  242. var accAuth *common.AccountCreationAuth
  243. if l2Tx.ToBJJ != common.EmptyBJJComp {
  244. // case: ToBJJ!=0:
  245. // if idx exist for EthAddr&BJJ use it
  246. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr,
  247. l2Tx.ToBJJ, l2Tx.TokenID)
  248. if err == nil {
  249. // account for ToEthAddr&ToBJJ already exist,
  250. // there is no need to create a new one.
  251. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  252. validTxs = append(validTxs, l2Tx)
  253. return validTxs, l1CoordinatorTxs, positionL1, nil
  254. }
  255. // if not, check if AccountCreationAuth exist for that
  256. // ToEthAddr
  257. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  258. if err != nil {
  259. // not found, l2Tx will not be added in the selection
  260. 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",
  261. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex()))
  262. }
  263. if accAuth.BJJ != l2Tx.ToBJJ {
  264. // if AccountCreationAuth.BJJ is not the same
  265. // than in the tx, tx is not accepted
  266. 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",
  267. l2Tx.ToIdx, l2Tx.ToEthAddr.Hex(), l2Tx.ToBJJ.String()))
  268. }
  269. validTxs = append(validTxs, l2Tx)
  270. } else {
  271. // case: ToBJJ==0:
  272. // if idx exist for EthAddr use it
  273. _, err := txsel.localAccountsDB.GetIdxByEthAddr(l2Tx.ToEthAddr, l2Tx.TokenID)
  274. if err == nil {
  275. // account for ToEthAddr already exist,
  276. // there is no need to create a new one.
  277. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  278. validTxs = append(validTxs, l2Tx)
  279. return validTxs, l1CoordinatorTxs, positionL1, nil
  280. }
  281. // if not, check if AccountCreationAuth exist for that ToEthAddr
  282. accAuth, err = txsel.l2db.GetAccountCreationAuth(l2Tx.ToEthAddr)
  283. if err != nil {
  284. // not found, l2Tx will not be added in the selection
  285. 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",
  286. l2Tx.ToIdx, l2Tx.ToEthAddr))
  287. }
  288. validTxs = append(validTxs, l2Tx)
  289. }
  290. // create L1CoordinatorTx for the accountCreation
  291. l1CoordinatorTx := common.L1Tx{
  292. Position: positionL1,
  293. UserOrigin: false,
  294. FromEthAddr: accAuth.EthAddr,
  295. FromBJJ: accAuth.BJJ,
  296. TokenID: l2Tx.TokenID,
  297. DepositAmount: big.NewInt(0),
  298. Type: common.TxTypeCreateAccountDeposit,
  299. }
  300. positionL1++
  301. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  302. } else if bytes.Equal(l2Tx.ToEthAddr.Bytes(), common.FFAddr.Bytes()) && l2Tx.ToBJJ != common.EmptyBJJComp {
  303. // if idx exist for EthAddr&BJJ use it
  304. _, err := txsel.localAccountsDB.GetIdxByEthAddrBJJ(l2Tx.ToEthAddr, l2Tx.ToBJJ,
  305. l2Tx.TokenID)
  306. if err == nil {
  307. // account for ToEthAddr&ToBJJ already exist, (where ToEthAddr==0xff)
  308. // there is no need to create a new one.
  309. // tx valid, StateDB will use the ToIdx==0 to define the AuxToIdx
  310. validTxs = append(validTxs, l2Tx)
  311. return validTxs, l1CoordinatorTxs, positionL1, nil
  312. }
  313. // if idx don't exist for EthAddr&BJJ,
  314. // coordinator can create a new account without
  315. // L1Authorization, as ToEthAddr==0xff
  316. // create L1CoordinatorTx for the accountCreation
  317. l1CoordinatorTx := common.L1Tx{
  318. Position: positionL1,
  319. UserOrigin: false,
  320. FromEthAddr: l2Tx.ToEthAddr,
  321. FromBJJ: l2Tx.ToBJJ,
  322. TokenID: l2Tx.TokenID,
  323. DepositAmount: big.NewInt(0),
  324. Type: common.TxTypeCreateAccountDeposit,
  325. }
  326. positionL1++
  327. l1CoordinatorTxs = append(l1CoordinatorTxs, l1CoordinatorTx)
  328. }
  329. return validTxs, l1CoordinatorTxs, positionL1, nil
  330. }
  331. func checkAlreadyPendingToCreate(l1CoordinatorTxs []common.L1Tx,
  332. addr ethCommon.Address, bjj babyjub.PublicKeyComp) bool {
  333. for i := 0; i < len(l1CoordinatorTxs); i++ {
  334. if bytes.Equal(l1CoordinatorTxs[i].FromEthAddr.Bytes(), addr.Bytes()) {
  335. if bjj == common.EmptyBJJComp {
  336. return true
  337. }
  338. if l1CoordinatorTxs[i].FromBJJ == bjj {
  339. return true
  340. }
  341. }
  342. }
  343. return false
  344. }
  345. // getL2Profitable returns the profitable selection of L2Txssorted by Nonce
  346. func (txsel *TxSelector) getL2Profitable(txs txs, max uint32) txs {
  347. sort.Sort(txs)
  348. if len(txs) < int(max) {
  349. return txs
  350. }
  351. txs = txs[:max]
  352. // sort l2Txs by Nonce. This can be done in many different ways, what
  353. // is needed is to output the txs where the Nonce of txs for each
  354. // Account is sorted, but the txs can not be grouped by sender Account
  355. // neither by Fee. This is because later on the Nonces will need to be
  356. // sequential for the zkproof generation.
  357. sort.SliceStable(txs, func(i, j int) bool {
  358. return txs[i].Nonce < txs[j].Nonce
  359. })
  360. return txs
  361. }