arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
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, forger_addr, bid_value, eth_block_num) 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 (forger_addr, eth_block_num, withdraw_addr, 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}