hermez-node/db/historydb/historydb.go
db/historydb/historydb.goRaw
1package historydb
2
3import (
4 "database/sql"
5 "errors"
6 "fmt"
7
8 ethCommon "github.com/ethereum/go-ethereum/common"
9 "github.com/hermeznetwork/hermez-node/common"
10 "github.com/hermeznetwork/hermez-node/db"
11 "github.com/iden3/go-iden3-crypto/babyjub"
12 "github.com/jmoiron/sqlx"
13
14 //nolint:errcheck // driver for postgres DB
15 _ "github.com/lib/pq"
16 "github.com/russross/meddler"
17)
18
19// TODO(Edu): Document here how HistoryDB is kept consistent
20
21// HistoryDB persist the historic of the rollup
22type HistoryDB struct {
23 db *sqlx.DB
24}
25
26// BlockData contains the information of a Block
27type BlockData struct {
28 block *common.Block
29 // Rollup
30 // L1UserTxs that were submitted in the block
31 L1UserTxs []common.L1Tx
32 Batches []BatchData
33 RegisteredTokens []common.Token
34 RollupVars *common.RollupVars
35 // Auction
36 Bids []common.Bid
37 Coordinators []common.Coordinator
38 AuctionVars *common.AuctionVars
39 // WithdrawalDelayer
40 // TODO: enable when common.WithdrawalDelayerVars is Merged from Synchronizer PR
41 // WithdrawalDelayerVars *common.WithdrawalDelayerVars
42}
43
44// BatchData contains the information of a Batch
45type BatchData struct {
46 // L1UserTxs that were forged in the batch
47 L1Batch bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer
48 L1UserTxs []common.L1Tx
49 L1CoordinatorTxs []common.L1Tx
50 L2Txs []common.L2Tx
51 CreatedAccounts []common.Account
52 ExitTree []common.ExitInfo
53 Batch *common.Batch
54}
55
56// NewHistoryDB initialize the DB
57func NewHistoryDB(db *sqlx.DB) *HistoryDB {
58 return &HistoryDB{db: db}
59}
60
61// AddBlock insert a block into the DB
62func (hdb *HistoryDB) AddBlock(block *common.Block) error { return hdb.addBlock(hdb.db, block) }
63func (hdb *HistoryDB) addBlock(d meddler.DB, block *common.Block) error {
64 return meddler.Insert(d, "block", block)
65}
66
67// AddBlocks inserts blocks into the DB
68func (hdb *HistoryDB) AddBlocks(blocks []common.Block) error {
69 return hdb.addBlocks(hdb.db, blocks)
70}
71
72func (hdb *HistoryDB) addBlocks(d meddler.DB, blocks []common.Block) error {
73 return db.BulkInsert(
74 d,
75 `INSERT INTO block (
76 eth_block_num,
77 timestamp,
78 hash
79 ) VALUES %s;`,
80 blocks[:],
81 )
82}
83
84// GetBlock retrieve a block from the DB, given a block number
85func (hdb *HistoryDB) GetBlock(blockNum int64) (*common.Block, error) {
86 block := &common.Block{}
87 err := meddler.QueryRow(
88 hdb.db, block,
89 "SELECT * FROM block WHERE eth_block_num = $1;", blockNum,
90 )
91 return block, err
92}
93
94// GetBlocks retrieve blocks from the DB, given a range of block numbers defined by from and to
95func (hdb *HistoryDB) GetBlocks(from, to int64) ([]*common.Block, error) {
96 var blocks []*common.Block
97 err := meddler.QueryAll(
98 hdb.db, &blocks,
99 "SELECT * FROM block WHERE $1 <= eth_block_num AND eth_block_num < $2;",
100 from, to,
101 )
102 return blocks, err
103}
104
105// GetLastBlock retrieve the block with the highest block number from the DB
106func (hdb *HistoryDB) GetLastBlock() (*common.Block, error) {
107 block := &common.Block{}
108 err := meddler.QueryRow(
109 hdb.db, block, "SELECT * FROM block ORDER BY eth_block_num DESC LIMIT 1;",
110 )
111 return block, err
112}
113
114// AddBatch insert a Batch into the DB
115func (hdb *HistoryDB) AddBatch(batch *common.Batch) error { return hdb.addBatch(hdb.db, batch) }
116func (hdb *HistoryDB) addBatch(d meddler.DB, batch *common.Batch) error {
117 return meddler.Insert(d, "batch", batch)
118}
119
120// AddBatches insert Bids into the DB
121func (hdb *HistoryDB) AddBatches(batches []common.Batch) error {
122 return hdb.addBatches(hdb.db, batches)
123}
124func (hdb *HistoryDB) addBatches(d meddler.DB, batches []common.Batch) error {
125 return db.BulkInsert(
126 d,
127 `INSERT INTO batch (
128 batch_num,
129 eth_block_num,
130 forger_addr,
131 fees_collected,
132 state_root,
133 num_accounts,
134 exit_root,
135 forge_l1_txs_num,
136 slot_num,
137 total_fees_usd
138 ) VALUES %s;`,
139 batches[:],
140 )
141}
142
143// GetBatches retrieve batches from the DB, given a range of batch numbers defined by from and to
144func (hdb *HistoryDB) GetBatches(from, to common.BatchNum) ([]*common.Batch, error) {
145 var batches []*common.Batch
146 err := meddler.QueryAll(
147 hdb.db, &batches,
148 "SELECT * FROM batch WHERE $1 <= batch_num AND batch_num < $2;",
149 from, to,
150 )
151 return batches, err
152}
153
154// GetLastBatchNum returns the BatchNum of the latest forged batch
155func (hdb *HistoryDB) GetLastBatchNum() (common.BatchNum, error) {
156 row := hdb.db.QueryRow("SELECT batch_num FROM batch ORDER BY batch_num DESC LIMIT 1;")
157 var batchNum common.BatchNum
158 return batchNum, row.Scan(&batchNum)
159}
160
161// GetLastL1TxsNum returns the greatest ForgeL1TxsNum in the DB. If there's no
162// batch in the DB (nil, nil) is returned.
163func (hdb *HistoryDB) GetLastL1TxsNum() (*int64, error) {
164 row := hdb.db.QueryRow("SELECT MAX(forge_l1_txs_num) FROM batch;")
165 lastL1TxsNum := new(int64)
166 return lastL1TxsNum, row.Scan(&lastL1TxsNum)
167}
168
169// Reorg deletes all the information that was added into the DB after the
170// lastValidBlock. If lastValidBlock is negative, all block information is
171// deleted.
172func (hdb *HistoryDB) Reorg(lastValidBlock int64) error {
173 var err error
174 if lastValidBlock < 0 {
175 _, err = hdb.db.Exec("DELETE FROM block;")
176 } else {
177 _, err = hdb.db.Exec("DELETE FROM block WHERE eth_block_num > $1;", lastValidBlock)
178 }
179 return err
180}
181
182// SyncPoD stores all the data that can be changed / added on a block in the PoD SC
183func (hdb *HistoryDB) SyncPoD(
184 blockNum uint64,
185 bids []common.Bid,
186 coordinators []common.Coordinator,
187 vars *common.AuctionVars,
188) error {
189 return nil
190}
191
192// AddBids insert Bids into the DB
193func (hdb *HistoryDB) AddBids(bids []common.Bid) error { return hdb.addBids(hdb.db, bids) }
194func (hdb *HistoryDB) addBids(d meddler.DB, bids []common.Bid) error {
195 // TODO: check the coordinator info
196 return db.BulkInsert(
197 d,
198 "INSERT INTO bid (slot_num, bid_value, eth_block_num, bidder_addr) VALUES %s;",
199 bids[:],
200 )
201}
202
203// GetBids return the bids
204func (hdb *HistoryDB) GetBids() ([]*common.Bid, error) {
205 var bids []*common.Bid
206 err := meddler.QueryAll(
207 hdb.db, &bids,
208 "SELECT * FROM bid;",
209 )
210 return bids, err
211}
212
213// AddCoordinators insert Coordinators into the DB
214func (hdb *HistoryDB) AddCoordinators(coordinators []common.Coordinator) error {
215 return hdb.addCoordinators(hdb.db, coordinators)
216}
217func (hdb *HistoryDB) addCoordinators(d meddler.DB, coordinators []common.Coordinator) error {
218 return db.BulkInsert(
219 d,
220 "INSERT INTO coordinator (bidder_addr, forger_addr, eth_block_num, url) VALUES %s;",
221 coordinators[:],
222 )
223}
224
225// AddExitTree insert Exit tree into the DB
226func (hdb *HistoryDB) AddExitTree(exitTree []common.ExitInfo) error {
227 return hdb.addExitTree(hdb.db, exitTree)
228}
229func (hdb *HistoryDB) addExitTree(d meddler.DB, exitTree []common.ExitInfo) error {
230 return db.BulkInsert(
231 d,
232 "INSERT INTO exit_tree (batch_num, account_idx, merkle_proof, balance, "+
233 "instant_withdrawn, delayed_withdraw_request, delayed_withdrawn) VALUES %s;",
234 exitTree[:],
235 )
236}
237
238// AddToken insert a token into the DB
239func (hdb *HistoryDB) AddToken(token *common.Token) error {
240 return meddler.Insert(hdb.db, "token", token)
241}
242
243// AddTokens insert tokens into the DB
244func (hdb *HistoryDB) AddTokens(tokens []common.Token) error { return hdb.addTokens(hdb.db, tokens) }
245func (hdb *HistoryDB) addTokens(d meddler.DB, tokens []common.Token) error {
246 return db.BulkInsert(
247 d,
248 `INSERT INTO token (
249 token_id,
250 eth_block_num,
251 eth_addr,
252 name,
253 symbol,
254 decimals,
255 usd,
256 usd_update
257 ) VALUES %s;`,
258 tokens[:],
259 )
260}
261
262// UpdateTokenValue updates the USD value of a token
263func (hdb *HistoryDB) UpdateTokenValue(tokenSymbol string, value float64) error {
264 _, err := hdb.db.Exec(
265 "UPDATE token SET usd = $1 WHERE symbol = $2;",
266 value, tokenSymbol,
267 )
268 return err
269}
270
271// GetTokens returns a list of tokens from the DB
272func (hdb *HistoryDB) GetTokens() ([]*common.Token, error) {
273 var tokens []*common.Token
274 err := meddler.QueryAll(
275 hdb.db, &tokens,
276 "SELECT * FROM token ORDER BY token_id;",
277 )
278 return tokens, err
279}
280
281// GetTokenSymbols returns all the token symbols from the DB
282func (hdb *HistoryDB) GetTokenSymbols() ([]string, error) {
283 var tokenSymbols []string
284 rows, err := hdb.db.Query("SELECT symbol FROM token;")
285 if err != nil {
286 return nil, err
287 }
288 sym := new(string)
289 for rows.Next() {
290 err = rows.Scan(sym)
291 if err != nil {
292 return nil, err
293 }
294 tokenSymbols = append(tokenSymbols, *sym)
295 }
296 return tokenSymbols, nil
297}
298
299// AddAccounts insert accounts into the DB
300func (hdb *HistoryDB) AddAccounts(accounts []common.Account) error {
301 return hdb.addAccounts(hdb.db, accounts)
302}
303func (hdb *HistoryDB) addAccounts(d meddler.DB, accounts []common.Account) error {
304 return db.BulkInsert(
305 d,
306 `INSERT INTO account (
307 idx,
308 token_id,
309 batch_num,
310 bjj,
311 eth_addr
312 ) VALUES %s;`,
313 accounts[:],
314 )
315}
316
317// GetAccounts returns a list of accounts from the DB
318func (hdb *HistoryDB) GetAccounts() ([]*common.Account, error) {
319 var accs []*common.Account
320 err := meddler.QueryAll(
321 hdb.db, &accs,
322 "SELECT * FROM account ORDER BY idx;",
323 )
324 return accs, err
325}
326
327// AddL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
328// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
329// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
330func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error { return hdb.addL1Txs(hdb.db, l1txs) }
331
332// addL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
333// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
334// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
335func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
336 txs := []common.Tx{}
337 for _, tx := range l1txs {
338 txs = append(txs, *(tx.Tx()))
339 }
340 return hdb.addTxs(d, txs)
341}
342
343// AddL2Txs inserts L2 txs to the DB. USD and FeeUSD will be set automatically before storing the tx.
344func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error { return hdb.addL2Txs(hdb.db, l2txs) }
345
346// addL2Txs inserts L2 txs to the DB. USD and FeeUSD will be set automatically before storing the tx.
347func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
348 txs := []common.Tx{}
349 for _, tx := range l2txs {
350 txs = append(txs, *(tx.Tx()))
351 }
352 return hdb.addTxs(d, txs)
353}
354
355func (hdb *HistoryDB) addTxs(d meddler.DB, txs []common.Tx) error {
356 return db.BulkInsert(
357 d,
358 `INSERT INTO tx (
359 is_l1,
360 id,
361 type,
362 position,
363 from_idx,
364 to_idx,
365 amount,
366 amount_f,
367 token_id,
368 amount_usd,
369 batch_num,
370 eth_block_num,
371 to_forge_l1_txs_num,
372 user_origin,
373 from_eth_addr,
374 from_bjj,
375 load_amount,
376 load_amount_f,
377 load_amount_usd,
378 fee,
379 fee_usd,
380 nonce
381 ) VALUES %s;`,
382 txs[:],
383 )
384}
385
386// GetTxs returns a list of txs from the DB
387func (hdb *HistoryDB) GetTxs() ([]*common.Tx, error) {
388 var txs []*common.Tx
389 err := meddler.QueryAll(
390 hdb.db, &txs,
391 `SELECT * FROM tx
392 ORDER BY (batch_num, position) ASC`,
393 )
394 return txs, err
395}
396
397// GetHistoryTxs returns a list of txs from the DB using the HistoryTx struct
398func (hdb *HistoryDB) GetHistoryTxs(
399 ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
400 tokenID, idx, batchNum *uint, txType *common.TxType,
401 offset, limit *uint, last bool,
402) ([]*HistoryTx, int, error) {
403 if ethAddr != nil && bjj != nil {
404 return nil, 0, errors.New("ethAddr and bjj are incompatible")
405 }
406 var query string
407 var args []interface{}
408 queryStr := `SELECT tx.*, token.token_id, token.eth_block_num AS token_block,
409 token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
410 token.usd_update, block.timestamp, count(*) OVER() AS total_items
411 FROM tx
412 INNER JOIN token ON tx.token_id = token.token_id
413 INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
414 // Apply filters
415 nextIsAnd := false
416 // ethAddr filter
417 if ethAddr != nil {
418 queryStr = `WITH acc AS
419 (select idx from account where eth_addr = ?) ` + queryStr
420 queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
421 nextIsAnd = true
422 args = append(args, ethAddr)
423 } else if bjj != nil { // bjj filter
424 queryStr = `WITH acc AS
425 (select idx from account where bjj = ?) ` + queryStr
426 queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
427 nextIsAnd = true
428 args = append(args, bjj)
429 }
430 // tokenID filter
431 if tokenID != nil {
432 if nextIsAnd {
433 queryStr += "AND "
434 } else {
435 queryStr += "WHERE "
436 }
437 queryStr += "tx.token_id = ? "
438 args = append(args, tokenID)
439 nextIsAnd = true
440 }
441 // idx filter
442 if idx != nil {
443 if nextIsAnd {
444 queryStr += "AND "
445 } else {
446 queryStr += "WHERE "
447 }
448 queryStr += "(tx.from_idx = ? OR tx.to_idx = ?) "
449 args = append(args, idx, idx)
450 nextIsAnd = true
451 }
452 // batchNum filter
453 if batchNum != nil {
454 if nextIsAnd {
455 queryStr += "AND "
456 } else {
457 queryStr += "WHERE "
458 }
459 queryStr += "tx.batch_num = ? "
460 args = append(args, batchNum)
461 nextIsAnd = true
462 }
463 // txType filter
464 if txType != nil {
465 if nextIsAnd {
466 queryStr += "AND "
467 } else {
468 queryStr += "WHERE "
469 }
470 queryStr += "tx.type = ? "
471 args = append(args, txType)
472 // nextIsAnd = true
473 }
474 // pagination
475 if last {
476 queryStr += "ORDER BY (batch_num, position) DESC NULLS FIRST "
477 } else {
478 queryStr += "ORDER BY (batch_num, position) ASC NULLS LAST "
479 queryStr += fmt.Sprintf("OFFSET %d ", *offset)
480 }
481 queryStr += fmt.Sprintf("LIMIT %d;", *limit)
482 query = hdb.db.Rebind(queryStr)
483 // log.Debug(query)
484 txs := []*HistoryTx{}
485 if err := meddler.QueryAll(hdb.db, &txs, query, args...); err != nil {
486 return nil, 0, err
487 }
488 if len(txs) == 0 {
489 return nil, 0, sql.ErrNoRows
490 } else if last {
491 tmp := []*HistoryTx{}
492 for i := len(txs) - 1; i >= 0; i-- {
493 tmp = append(tmp, txs[i])
494 }
495 txs = tmp
496 }
497 return txs, txs[0].TotalItems, nil
498}
499
500// GetTx returns a tx from the DB
501func (hdb *HistoryDB) GetTx(txID common.TxID) (*common.Tx, error) {
502 tx := new(common.Tx)
503 return tx, meddler.QueryRow(
504 hdb.db, tx,
505 "SELECT * FROM tx WHERE id = $1;",
506 txID,
507 )
508}
509
510// // GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account
511// // TODO: This is currently not used. Figure out if it should be used somewhere or removed.
512// func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) {
513// var txs []*common.Tx
514// err := meddler.QueryAll(
515// hdb.db, &txs,
516// "SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;",
517// toForgeL1TxsNum,
518// )
519// return txs, err
520// }
521
522// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
523
524// GetLastTxsPosition for a given to_forge_l1_txs_num
525func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) {
526 row := hdb.db.QueryRow("SELECT MAX(position) FROM tx WHERE to_forge_l1_txs_num = $1;", toForgeL1TxsNum)
527 var lastL1TxsPosition int
528 return lastL1TxsPosition, row.Scan(&lastL1TxsPosition)
529}
530
531// AddBlockSCData stores all the information of a block retrieved by the Synchronizer
532func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
533 txn, err := hdb.db.Begin()
534 if err != nil {
535 return err
536 }
537 defer func() {
538 if err != nil {
539 err = txn.Rollback()
540 }
541 }()
542
543 // Add block
544 err = hdb.addBlock(txn, blockData.block)
545 if err != nil {
546 return err
547 }
548
549 // Add l1 Txs
550 if len(blockData.L1UserTxs) > 0 {
551 err = hdb.addL1Txs(txn, blockData.L1UserTxs)
552 if err != nil {
553 return err
554 }
555 }
556
557 // Add Tokens
558 if len(blockData.RegisteredTokens) > 0 {
559 err = hdb.addTokens(txn, blockData.RegisteredTokens)
560 if err != nil {
561 return err
562 }
563 }
564
565 // Add Bids
566 if len(blockData.Bids) > 0 {
567 err = hdb.addBids(txn, blockData.Bids)
568 if err != nil {
569 return err
570 }
571 }
572
573 // Add Coordinators
574 if len(blockData.Coordinators) > 0 {
575 err = hdb.addCoordinators(txn, blockData.Coordinators)
576 if err != nil {
577 return err
578 }
579 }
580
581 // Add Batches
582 for _, batch := range blockData.Batches {
583 // Add Batch: this will trigger an update on the DB
584 // that will set the batch num of forged L1 txs in this batch
585 err = hdb.addBatch(txn, batch.Batch)
586 if err != nil {
587 return err
588 }
589
590 // Add unforged l1 Txs
591 if batch.L1Batch {
592 if len(batch.L1CoordinatorTxs) > 0 {
593 err = hdb.addL1Txs(txn, batch.L1CoordinatorTxs)
594 if err != nil {
595 return err
596 }
597 }
598 }
599
600 // Add l2 Txs
601 if len(batch.L2Txs) > 0 {
602 err = hdb.addL2Txs(txn, batch.L2Txs)
603 if err != nil {
604 return err
605 }
606 }
607
608 // Add accounts
609 if len(batch.CreatedAccounts) > 0 {
610 err = hdb.addAccounts(txn, batch.CreatedAccounts)
611 if err != nil {
612 return err
613 }
614 }
615
616 // Add exit tree
617 if len(batch.ExitTree) > 0 {
618 err = hdb.addExitTree(txn, batch.ExitTree)
619 if err != nil {
620 return err
621 }
622 }
623
624 // TODO: INSERT CONTRACTS VARS
625 }
626
627 return txn.Commit()
628}