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.

285 lines
9.0 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. package l2db
  2. import (
  3. "errors"
  4. "math/big"
  5. "time"
  6. ethCommon "github.com/ethereum/go-ethereum/common"
  7. "github.com/hermeznetwork/hermez-node/common"
  8. "github.com/iden3/go-iden3-crypto/babyjub"
  9. "github.com/jmoiron/sqlx"
  10. //nolint:errcheck // driver for postgres DB
  11. _ "github.com/lib/pq"
  12. "github.com/russross/meddler"
  13. )
  14. // TODO(Edu): Check DB consistency while there's concurrent use from Coordinator/TxSelector & API
  15. // L2DB stores L2 txs and authorization registers received by the coordinator and keeps them until they are no longer relevant
  16. // due to them being forged or invalid after a safety period
  17. type L2DB struct {
  18. db *sqlx.DB
  19. safetyPeriod common.BatchNum
  20. ttl time.Duration
  21. maxTxs uint32
  22. }
  23. // NewL2DB creates a L2DB.
  24. // To create it, it's needed db connection, safety period expressed in batches,
  25. // maxTxs that the DB should have and TTL (time to live) for pending txs.
  26. func NewL2DB(db *sqlx.DB, safetyPeriod common.BatchNum, maxTxs uint32, TTL time.Duration) *L2DB {
  27. return &L2DB{
  28. db: db,
  29. safetyPeriod: safetyPeriod,
  30. ttl: TTL,
  31. maxTxs: maxTxs,
  32. }
  33. }
  34. // DB returns a pointer to the L2DB.db. This method should be used only for
  35. // internal testing purposes.
  36. func (l2db *L2DB) DB() *sqlx.DB {
  37. return l2db.db
  38. }
  39. // AddAccountCreationAuth inserts an account creation authorization into the DB
  40. func (l2db *L2DB) AddAccountCreationAuth(auth *common.AccountCreationAuth) error {
  41. return meddler.Insert(l2db.db, "account_creation_auth", auth)
  42. }
  43. // GetAccountCreationAuth returns an account creation authorization into the DB
  44. func (l2db *L2DB) GetAccountCreationAuth(addr *ethCommon.Address) (*common.AccountCreationAuth, error) {
  45. if addr == nil {
  46. return nil, errors.New("addr cannot be nil")
  47. }
  48. auth := new(common.AccountCreationAuth)
  49. return auth, meddler.QueryRow(
  50. l2db.db, auth,
  51. "SELECT * FROM account_creation_auth WHERE eth_addr = $1;",
  52. addr,
  53. )
  54. }
  55. // AddTxTest inserts a tx into the L2DB. This is useful for test purposes,
  56. // but in production txs will only be inserted through the API (method TBD)
  57. func (l2db *L2DB) AddTxTest(tx *common.PoolL2Tx) error {
  58. type withouUSD struct {
  59. TxID common.TxID `meddler:"tx_id"`
  60. FromIdx common.Idx `meddler:"from_idx"`
  61. ToIdx *common.Idx `meddler:"to_idx"`
  62. ToEthAddr *ethCommon.Address `meddler:"to_eth_addr"`
  63. ToBJJ *babyjub.PublicKey `meddler:"to_bjj"`
  64. TokenID common.TokenID `meddler:"token_id"`
  65. Amount *big.Int `meddler:"amount,bigint"`
  66. AmountFloat float64 `meddler:"amount_f"`
  67. Fee common.FeeSelector `meddler:"fee"`
  68. Nonce common.Nonce `meddler:"nonce"`
  69. State common.PoolL2TxState `meddler:"state"`
  70. Signature *babyjub.Signature `meddler:"signature"`
  71. Timestamp time.Time `meddler:"timestamp,utctime"`
  72. BatchNum *common.BatchNum `meddler:"batch_num"`
  73. RqFromIdx *common.Idx `meddler:"rq_from_idx"`
  74. RqToIdx *common.Idx `meddler:"rq_to_idx"`
  75. RqToEthAddr *ethCommon.Address `meddler:"rq_to_eth_addr"`
  76. RqToBJJ *babyjub.PublicKey `meddler:"rq_to_bjj"`
  77. RqTokenID *common.TokenID `meddler:"rq_token_id"`
  78. RqAmount *big.Int `meddler:"rq_amount,bigintnull"`
  79. RqFee *common.FeeSelector `meddler:"rq_fee"`
  80. RqNonce *uint64 `meddler:"rq_nonce"`
  81. Type common.TxType `meddler:"tx_type"`
  82. }
  83. return meddler.Insert(l2db.db, "tx_pool", &withouUSD{
  84. TxID: tx.TxID,
  85. FromIdx: tx.FromIdx,
  86. ToIdx: tx.ToIdx,
  87. ToEthAddr: tx.ToEthAddr,
  88. ToBJJ: tx.ToBJJ,
  89. TokenID: tx.TokenID,
  90. Amount: tx.Amount,
  91. AmountFloat: tx.AmountFloat,
  92. Fee: tx.Fee,
  93. Nonce: tx.Nonce,
  94. State: tx.State,
  95. Signature: tx.Signature,
  96. Timestamp: tx.Timestamp,
  97. BatchNum: tx.BatchNum,
  98. RqFromIdx: tx.RqFromIdx,
  99. RqToIdx: tx.RqToIdx,
  100. RqToEthAddr: tx.RqToEthAddr,
  101. RqToBJJ: tx.RqToBJJ,
  102. RqTokenID: tx.RqTokenID,
  103. RqAmount: tx.RqAmount,
  104. RqFee: tx.RqFee,
  105. RqNonce: tx.RqNonce,
  106. Type: tx.Type,
  107. })
  108. }
  109. // selectPoolTx select part of queries to get common.PoolL2Tx
  110. const selectPoolTx = `SELECT tx_pool.*, token.usd * tx_pool.amount_f AS value_usd,
  111. fee_percentage(tx_pool.fee::NUMERIC) * token.usd * tx_pool.amount_f AS fee_usd, token.usd_update
  112. FROM tx_pool INNER JOIN token ON tx_pool.token_id = token.token_id `
  113. // GetTx return the specified Tx
  114. func (l2db *L2DB) GetTx(txID common.TxID) (*common.PoolL2Tx, error) {
  115. tx := new(common.PoolL2Tx)
  116. return tx, meddler.QueryRow(
  117. l2db.db, tx,
  118. selectPoolTx+"WHERE tx_id = $1;",
  119. txID,
  120. )
  121. }
  122. // GetPendingTxs return all the pending txs of the L2DB, that have a non NULL AbsoluteFee
  123. func (l2db *L2DB) GetPendingTxs() ([]*common.PoolL2Tx, error) {
  124. var txs []*common.PoolL2Tx
  125. err := meddler.QueryAll(
  126. l2db.db, &txs,
  127. selectPoolTx+"WHERE state = $1 AND token.usd IS NOT NULL",
  128. common.PoolL2TxStatePending,
  129. )
  130. return txs, err
  131. }
  132. // StartForging updates the state of the transactions that will begin the forging process.
  133. // The state of the txs referenced by txIDs will be changed from Pending -> Forging
  134. func (l2db *L2DB) StartForging(txIDs []common.TxID, batchNum common.BatchNum) error {
  135. query, args, err := sqlx.In(
  136. `UPDATE tx_pool
  137. SET state = ?, batch_num = ?
  138. WHERE state = ? AND tx_id IN (?);`,
  139. common.PoolL2TxStateForging,
  140. batchNum,
  141. common.PoolL2TxStatePending,
  142. txIDs,
  143. )
  144. if err != nil {
  145. return err
  146. }
  147. query = l2db.db.Rebind(query)
  148. _, err = l2db.db.Exec(query, args...)
  149. return err
  150. }
  151. // DoneForging updates the state of the transactions that have been forged
  152. // so the state of the txs referenced by txIDs will be changed from Forging -> Forged
  153. func (l2db *L2DB) DoneForging(txIDs []common.TxID, batchNum common.BatchNum) error {
  154. query, args, err := sqlx.In(
  155. `UPDATE tx_pool
  156. SET state = ?, batch_num = ?
  157. WHERE state = ? AND tx_id IN (?);`,
  158. common.PoolL2TxStateForged,
  159. batchNum,
  160. common.PoolL2TxStateForging,
  161. txIDs,
  162. )
  163. if err != nil {
  164. return err
  165. }
  166. query = l2db.db.Rebind(query)
  167. _, err = l2db.db.Exec(query, args...)
  168. return err
  169. }
  170. // InvalidateTxs updates the state of the transactions that are invalid.
  171. // The state of the txs referenced by txIDs will be changed from * -> Invalid
  172. func (l2db *L2DB) InvalidateTxs(txIDs []common.TxID, batchNum common.BatchNum) error {
  173. query, args, err := sqlx.In(
  174. `UPDATE tx_pool
  175. SET state = ?, batch_num = ?
  176. WHERE tx_id IN (?);`,
  177. common.PoolL2TxStateInvalid,
  178. batchNum,
  179. txIDs,
  180. )
  181. if err != nil {
  182. return err
  183. }
  184. query = l2db.db.Rebind(query)
  185. _, err = l2db.db.Exec(query, args...)
  186. return err
  187. }
  188. // CheckNonces invalidate txs with nonces that are smaller or equal than their respective accounts nonces.
  189. // The state of the affected txs will be changed from Pending -> Invalid
  190. func (l2db *L2DB) CheckNonces(updatedAccounts []common.Account, batchNum common.BatchNum) (err error) {
  191. txn, err := l2db.db.Begin()
  192. if err != nil {
  193. return err
  194. }
  195. defer func() {
  196. // Rollback the transaction if there was an error.
  197. if err != nil {
  198. err = txn.Rollback()
  199. }
  200. }()
  201. for i := 0; i < len(updatedAccounts); i++ {
  202. _, err = txn.Exec(
  203. `UPDATE tx_pool
  204. SET state = $1, batch_num = $2
  205. WHERE state = $3 AND from_idx = $4 AND nonce <= $5;`,
  206. common.PoolL2TxStateInvalid,
  207. batchNum,
  208. common.PoolL2TxStatePending,
  209. updatedAccounts[i].Idx,
  210. updatedAccounts[i].Nonce,
  211. )
  212. if err != nil {
  213. return err
  214. }
  215. }
  216. return txn.Commit()
  217. }
  218. // Reorg updates the state of txs that were updated in a batch that has been discarted due to a blockchain reorg.
  219. // The state of the affected txs can change form Forged -> Pending or from Invalid -> Pending
  220. func (l2db *L2DB) Reorg(lastValidBatch common.BatchNum) error {
  221. _, err := l2db.db.Exec(
  222. `UPDATE tx_pool SET batch_num = NULL, state = $1
  223. WHERE (state = $2 OR state = $3) AND batch_num > $4`,
  224. common.PoolL2TxStatePending,
  225. common.PoolL2TxStateForged,
  226. common.PoolL2TxStateInvalid,
  227. lastValidBatch,
  228. )
  229. return err
  230. }
  231. // Purge deletes transactions that have been forged or marked as invalid for longer than the safety period
  232. // it also deletes txs that has been in the L2DB for longer than the ttl if maxTxs has been exceeded
  233. func (l2db *L2DB) Purge(currentBatchNum common.BatchNum) (err error) {
  234. txn, err := l2db.db.Begin()
  235. if err != nil {
  236. return err
  237. }
  238. defer func() {
  239. // Rollback the transaction if there was an error.
  240. if err != nil {
  241. err = txn.Rollback()
  242. }
  243. }()
  244. // Delete pending txs that have been in the pool after the TTL if maxTxs is reached
  245. now := time.Now().UTC().Unix()
  246. _, err = txn.Exec(
  247. `DELETE FROM tx_pool WHERE (SELECT count(*) FROM tx_pool) > $1 AND timestamp < $2`,
  248. l2db.maxTxs,
  249. time.Unix(now-int64(l2db.ttl.Seconds()), 0),
  250. )
  251. if err != nil {
  252. return err
  253. }
  254. // Delete txs that have been marked as forged / invalid after the safety period
  255. _, err = txn.Exec(
  256. `DELETE FROM tx_pool
  257. WHERE batch_num < $1 AND (state = $2 OR state = $3)`,
  258. currentBatchNum-l2db.safetyPeriod,
  259. common.PoolL2TxStateForged,
  260. common.PoolL2TxStateInvalid,
  261. )
  262. if err != nil {
  263. return err
  264. }
  265. return txn.Commit()
  266. }