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