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	"math/big"
  8
  9	ethCommon "github.com/ethereum/go-ethereum/common"
 10	"github.com/hermeznetwork/hermez-node/common"
 11	"github.com/hermeznetwork/hermez-node/db"
 12	"github.com/hermeznetwork/hermez-node/log"
 13	"github.com/iden3/go-iden3-crypto/babyjub"
 14	"github.com/jmoiron/sqlx"
 15
 16	//nolint:errcheck // driver for postgres DB
 17	_ "github.com/lib/pq"
 18	"github.com/russross/meddler"
 19)
 20
 21const (
 22	// OrderAsc indicates ascending order when using pagination
 23	OrderAsc = "ASC"
 24	// OrderDesc indicates descending order when using pagination
 25	OrderDesc = "DESC"
 26)
 27
 28// TODO(Edu): Document here how HistoryDB is kept consistent
 29
 30// HistoryDB persist the historic of the rollup
 31type HistoryDB struct {
 32	db *sqlx.DB
 33}
 34
 35// BlockData contains the information of a Block
 36type BlockData struct {
 37	Block *common.Block
 38	// Rollup
 39	// L1UserTxs that were submitted in the block
 40	L1UserTxs        []common.L1Tx
 41	Batches          []BatchData
 42	RegisteredTokens []common.Token
 43	RollupVars       *common.RollupVars
 44	// Auction
 45	Bids                []common.Bid
 46	Coordinators        []common.Coordinator
 47	AuctionVars         *common.AuctionVars
 48	WithdrawDelayerVars *common.WithdrawDelayerVars
 49	// TODO: enable when common.WithdrawalDelayerVars is Merged from Synchronizer PR
 50	// WithdrawalDelayerVars *common.WithdrawalDelayerVars
 51}
 52
 53// BatchData contains the information of a Batch
 54type BatchData struct {
 55	// L1UserTxs that were forged in the batch
 56	L1Batch          bool // TODO: Remove once Batch.ForgeL1TxsNum is a pointer
 57	L1UserTxs        []common.L1Tx
 58	L1CoordinatorTxs []common.L1Tx
 59	L2Txs            []common.L2Tx
 60	CreatedAccounts  []common.Account
 61	ExitTree         []common.ExitInfo
 62	Batch            *common.Batch
 63}
 64
 65// NewBatchData creates an empty BatchData with the slices initialized.
 66func NewBatchData() *BatchData {
 67	return &BatchData{
 68		L1Batch:          false,
 69		L1UserTxs:        make([]common.L1Tx, 0),
 70		L1CoordinatorTxs: make([]common.L1Tx, 0),
 71		L2Txs:            make([]common.L2Tx, 0),
 72		CreatedAccounts:  make([]common.Account, 0),
 73		ExitTree:         make([]common.ExitInfo, 0),
 74		Batch:            &common.Batch{},
 75	}
 76}
 77
 78// NewHistoryDB initialize the DB
 79func NewHistoryDB(db *sqlx.DB) *HistoryDB {
 80	return &HistoryDB{db: db}
 81}
 82
 83// AddBlock insert a block into the DB
 84func (hdb *HistoryDB) AddBlock(block *common.Block) error { return hdb.addBlock(hdb.db, block) }
 85func (hdb *HistoryDB) addBlock(d meddler.DB, block *common.Block) error {
 86	return meddler.Insert(d, "block", block)
 87}
 88
 89// AddBlocks inserts blocks into the DB
 90func (hdb *HistoryDB) AddBlocks(blocks []common.Block) error {
 91	return hdb.addBlocks(hdb.db, blocks)
 92}
 93
 94func (hdb *HistoryDB) addBlocks(d meddler.DB, blocks []common.Block) error {
 95	return db.BulkInsert(
 96		d,
 97		`INSERT INTO block (
 98			eth_block_num,
 99			timestamp,
100			hash
101		) VALUES %s;`,
102		blocks[:],
103	)
104}
105
106// GetBlock retrieve a block from the DB, given a block number
107func (hdb *HistoryDB) GetBlock(blockNum int64) (*common.Block, error) {
108	block := &common.Block{}
109	err := meddler.QueryRow(
110		hdb.db, block,
111		"SELECT * FROM block WHERE eth_block_num = $1;", blockNum,
112	)
113	return block, err
114}
115
116// GetBlocks retrieve blocks from the DB, given a range of block numbers defined by from and to
117func (hdb *HistoryDB) GetBlocks(from, to int64) ([]common.Block, error) {
118	var blocks []*common.Block
119	err := meddler.QueryAll(
120		hdb.db, &blocks,
121		"SELECT * FROM block WHERE $1 <= eth_block_num AND eth_block_num < $2;",
122		from, to,
123	)
124	return db.SlicePtrsToSlice(blocks).([]common.Block), err
125}
126
127// GetLastBlock retrieve the block with the highest block number from the DB
128func (hdb *HistoryDB) GetLastBlock() (*common.Block, error) {
129	block := &common.Block{}
130	err := meddler.QueryRow(
131		hdb.db, block, "SELECT * FROM block ORDER BY eth_block_num DESC LIMIT 1;",
132	)
133	return block, err
134}
135
136// AddBatch insert a Batch into the DB
137func (hdb *HistoryDB) AddBatch(batch *common.Batch) error { return hdb.addBatch(hdb.db, batch) }
138func (hdb *HistoryDB) addBatch(d meddler.DB, batch *common.Batch) error {
139	return meddler.Insert(d, "batch", batch)
140}
141
142// AddBatches insert Bids into the DB
143func (hdb *HistoryDB) AddBatches(batches []common.Batch) error {
144	return hdb.addBatches(hdb.db, batches)
145}
146func (hdb *HistoryDB) addBatches(d meddler.DB, batches []common.Batch) error {
147	// TODO: Calculate and insert total_fees_usd
148	return db.BulkInsert(
149		d,
150		`INSERT INTO batch (
151			batch_num,
152			eth_block_num,
153			forger_addr,
154			fees_collected,
155			state_root,
156			num_accounts,
157			exit_root,
158			forge_l1_txs_num,
159			slot_num
160		) VALUES %s;`,
161		batches[:],
162	)
163}
164
165// GetBatches retrieve batches from the DB, given a range of batch numbers defined by from and to
166func (hdb *HistoryDB) GetBatches(from, to common.BatchNum) ([]common.Batch, error) {
167	var batches []*common.Batch
168	err := meddler.QueryAll(
169		hdb.db, &batches,
170		"SELECT * FROM batch WHERE $1 <= batch_num AND batch_num < $2;",
171		from, to,
172	)
173	return db.SlicePtrsToSlice(batches).([]common.Batch), err
174}
175
176// GetLastBatchNum returns the BatchNum of the latest forged batch
177func (hdb *HistoryDB) GetLastBatchNum() (common.BatchNum, error) {
178	row := hdb.db.QueryRow("SELECT batch_num FROM batch ORDER BY batch_num DESC LIMIT 1;")
179	var batchNum common.BatchNum
180	return batchNum, row.Scan(&batchNum)
181}
182
183// GetLastL1TxsNum returns the greatest ForgeL1TxsNum in the DB.  If there's no
184// batch in the DB (nil, nil) is returned.
185func (hdb *HistoryDB) GetLastL1TxsNum() (*int64, error) {
186	row := hdb.db.QueryRow("SELECT MAX(forge_l1_txs_num) FROM batch;")
187	lastL1TxsNum := new(int64)
188	return lastL1TxsNum, row.Scan(&lastL1TxsNum)
189}
190
191// Reorg deletes all the information that was added into the DB after the
192// lastValidBlock.  If lastValidBlock is negative, all block information is
193// deleted.
194func (hdb *HistoryDB) Reorg(lastValidBlock int64) error {
195	var err error
196	if lastValidBlock < 0 {
197		_, err = hdb.db.Exec("DELETE FROM block;")
198	} else {
199		_, err = hdb.db.Exec("DELETE FROM block WHERE eth_block_num > $1;", lastValidBlock)
200	}
201	return err
202}
203
204// SyncPoD stores all the data that can be changed / added on a block in the PoD SC
205func (hdb *HistoryDB) SyncPoD(
206	blockNum uint64,
207	bids []common.Bid,
208	coordinators []common.Coordinator,
209	vars *common.AuctionVars,
210) error {
211	return nil
212}
213
214// AddBids insert Bids into the DB
215func (hdb *HistoryDB) AddBids(bids []common.Bid) error { return hdb.addBids(hdb.db, bids) }
216func (hdb *HistoryDB) addBids(d meddler.DB, bids []common.Bid) error {
217	// TODO: check the coordinator info
218	return db.BulkInsert(
219		d,
220		"INSERT INTO bid (slot_num, bid_value, eth_block_num, bidder_addr) VALUES %s;",
221		bids[:],
222	)
223}
224
225// GetBids return the bids
226func (hdb *HistoryDB) GetBids() ([]common.Bid, error) {
227	var bids []*common.Bid
228	err := meddler.QueryAll(
229		hdb.db, &bids,
230		"SELECT * FROM bid;",
231	)
232	return db.SlicePtrsToSlice(bids).([]common.Bid), err
233}
234
235// AddCoordinators insert Coordinators into the DB
236func (hdb *HistoryDB) AddCoordinators(coordinators []common.Coordinator) error {
237	return hdb.addCoordinators(hdb.db, coordinators)
238}
239func (hdb *HistoryDB) addCoordinators(d meddler.DB, coordinators []common.Coordinator) error {
240	return db.BulkInsert(
241		d,
242		"INSERT INTO coordinator (bidder_addr, forger_addr, eth_block_num, url) VALUES %s;",
243		coordinators[:],
244	)
245}
246
247// AddExitTree insert Exit tree into the DB
248func (hdb *HistoryDB) AddExitTree(exitTree []common.ExitInfo) error {
249	return hdb.addExitTree(hdb.db, exitTree)
250}
251func (hdb *HistoryDB) addExitTree(d meddler.DB, exitTree []common.ExitInfo) error {
252	return db.BulkInsert(
253		d,
254		"INSERT INTO exit_tree (batch_num, account_idx, merkle_proof, balance, "+
255			"instant_withdrawn, delayed_withdraw_request, delayed_withdrawn) VALUES %s;",
256		exitTree[:],
257	)
258}
259
260// AddToken insert a token into the DB
261func (hdb *HistoryDB) AddToken(token *common.Token) error {
262	return meddler.Insert(hdb.db, "token", token)
263}
264
265// AddTokens insert tokens into the DB
266func (hdb *HistoryDB) AddTokens(tokens []common.Token) error { return hdb.addTokens(hdb.db, tokens) }
267func (hdb *HistoryDB) addTokens(d meddler.DB, tokens []common.Token) error {
268	return db.BulkInsert(
269		d,
270		`INSERT INTO token (
271			token_id,
272			eth_block_num,
273			eth_addr,
274			name,
275			symbol,
276			decimals
277		) VALUES %s;`,
278		tokens[:],
279	)
280}
281
282// UpdateTokenValue updates the USD value of a token
283func (hdb *HistoryDB) UpdateTokenValue(tokenSymbol string, value float64) error {
284	_, err := hdb.db.Exec(
285		"UPDATE token SET usd = $1 WHERE symbol = $2;",
286		value, tokenSymbol,
287	)
288	return err
289}
290
291// GetToken returns a token from the DB given a TokenID
292func (hdb *HistoryDB) GetToken(tokenID common.TokenID) (*TokenRead, error) {
293	token := &TokenRead{}
294	err := meddler.QueryRow(
295		hdb.db, token, `SELECT * FROM token WHERE token_id = $1;`, tokenID,
296	)
297	return token, err
298}
299
300// GetTokens returns a list of tokens from the DB
301func (hdb *HistoryDB) GetTokens(ids []common.TokenID, symbols []string, name string, fromItem, limit *uint, order string) ([]TokenRead, *db.Pagination, error) {
302	var query string
303	var args []interface{}
304	queryStr := `SELECT * , COUNT(*) OVER() AS total_items, MIN(token.item_id) OVER() AS first_item, MAX(token.item_id) OVER() AS last_item FROM token `
305	// Apply filters
306	nextIsAnd := false
307	if len(ids) > 0 {
308		queryStr += "WHERE token_id IN (?) "
309		nextIsAnd = true
310		args = append(args, ids)
311	}
312	if len(symbols) > 0 {
313		if nextIsAnd {
314			queryStr += "AND "
315		} else {
316			queryStr += "WHERE "
317		}
318		queryStr += "symbol IN (?) "
319		args = append(args, symbols)
320		nextIsAnd = true
321	}
322	if name != "" {
323		if nextIsAnd {
324			queryStr += "AND "
325		} else {
326			queryStr += "WHERE "
327		}
328		queryStr += "name ~ ? "
329		args = append(args, name)
330		nextIsAnd = true
331	}
332	if fromItem != nil {
333		if nextIsAnd {
334			queryStr += "AND "
335		} else {
336			queryStr += "WHERE "
337		}
338		if order == OrderAsc {
339			queryStr += "item_id >= ? "
340		} else {
341			queryStr += "item_id <= ? "
342		}
343		args = append(args, fromItem)
344	}
345	// pagination
346	queryStr += "ORDER BY item_id "
347	if order == OrderAsc {
348		queryStr += "ASC "
349	} else {
350		queryStr += "DESC "
351	}
352	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
353	query, argsQ, err := sqlx.In(queryStr, args...)
354	if err != nil {
355		return nil, nil, err
356	}
357	query = hdb.db.Rebind(query)
358	tokens := []*TokenRead{}
359	if err := meddler.QueryAll(hdb.db, &tokens, query, argsQ...); err != nil {
360		return nil, nil, err
361	}
362	if len(tokens) == 0 {
363		return nil, nil, sql.ErrNoRows
364	}
365	return db.SlicePtrsToSlice(tokens).([]TokenRead), &db.Pagination{
366		TotalItems: tokens[0].TotalItems,
367		FirstItem:  tokens[0].FirstItem,
368		LastItem:   tokens[0].LastItem,
369	}, nil
370}
371
372// GetTokenSymbols returns all the token symbols from the DB
373func (hdb *HistoryDB) GetTokenSymbols() ([]string, error) {
374	var tokenSymbols []string
375	rows, err := hdb.db.Query("SELECT symbol FROM token;")
376	if err != nil {
377		return nil, err
378	}
379	sym := new(string)
380	for rows.Next() {
381		err = rows.Scan(sym)
382		if err != nil {
383			return nil, err
384		}
385		tokenSymbols = append(tokenSymbols, *sym)
386	}
387	return tokenSymbols, nil
388}
389
390// AddAccounts insert accounts into the DB
391func (hdb *HistoryDB) AddAccounts(accounts []common.Account) error {
392	return hdb.addAccounts(hdb.db, accounts)
393}
394func (hdb *HistoryDB) addAccounts(d meddler.DB, accounts []common.Account) error {
395	return db.BulkInsert(
396		d,
397		`INSERT INTO account (
398			idx,
399			token_id,
400			batch_num,
401			bjj,
402			eth_addr
403		) VALUES %s;`,
404		accounts[:],
405	)
406}
407
408// GetAccounts returns a list of accounts from the DB
409func (hdb *HistoryDB) GetAccounts() ([]common.Account, error) {
410	var accs []*common.Account
411	err := meddler.QueryAll(
412		hdb.db, &accs,
413		"SELECT * FROM account ORDER BY idx;",
414	)
415	return db.SlicePtrsToSlice(accs).([]common.Account), err
416}
417
418// AddL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
419// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
420// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
421func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error { return hdb.addL1Txs(hdb.db, l1txs) }
422
423// addL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
424// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
425// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
426func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
427	txs := []txWrite{}
428	for i := 0; i < len(l1txs); i++ {
429		af := new(big.Float).SetInt(l1txs[i].Amount)
430		amountFloat, _ := af.Float64()
431		laf := new(big.Float).SetInt(l1txs[i].LoadAmount)
432		loadAmountFloat, _ := laf.Float64()
433		txs = append(txs, txWrite{
434			// Generic
435			IsL1:        true,
436			TxID:        l1txs[i].TxID,
437			Type:        l1txs[i].Type,
438			Position:    l1txs[i].Position,
439			FromIdx:     &l1txs[i].FromIdx,
440			ToIdx:       l1txs[i].ToIdx,
441			Amount:      l1txs[i].Amount,
442			AmountFloat: amountFloat,
443			TokenID:     l1txs[i].TokenID,
444			BatchNum:    l1txs[i].BatchNum,
445			EthBlockNum: l1txs[i].EthBlockNum,
446			// L1
447			ToForgeL1TxsNum: l1txs[i].ToForgeL1TxsNum,
448			UserOrigin:      &l1txs[i].UserOrigin,
449			FromEthAddr:     &l1txs[i].FromEthAddr,
450			FromBJJ:         l1txs[i].FromBJJ,
451			LoadAmount:      l1txs[i].LoadAmount,
452			LoadAmountFloat: &loadAmountFloat,
453		})
454	}
455	return hdb.addTxs(d, txs)
456}
457
458// AddL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
459func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error { return hdb.addL2Txs(hdb.db, l2txs) }
460
461// addL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
462func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
463	txs := []txWrite{}
464	for i := 0; i < len(l2txs); i++ {
465		f := new(big.Float).SetInt(l2txs[i].Amount)
466		amountFloat, _ := f.Float64()
467		txs = append(txs, txWrite{
468			// Generic
469			IsL1:        false,
470			TxID:        l2txs[i].TxID,
471			Type:        l2txs[i].Type,
472			Position:    l2txs[i].Position,
473			FromIdx:     &l2txs[i].FromIdx,
474			ToIdx:       l2txs[i].ToIdx,
475			Amount:      l2txs[i].Amount,
476			AmountFloat: amountFloat,
477			BatchNum:    &l2txs[i].BatchNum,
478			EthBlockNum: l2txs[i].EthBlockNum,
479			// L2
480			Fee:   &l2txs[i].Fee,
481			Nonce: &l2txs[i].Nonce,
482		})
483	}
484	return hdb.addTxs(d, txs)
485}
486
487func (hdb *HistoryDB) addTxs(d meddler.DB, txs []txWrite) error {
488	return db.BulkInsert(
489		d,
490		`INSERT INTO tx (
491			is_l1,
492			id,
493			type,
494			position,
495			from_idx,
496			to_idx,
497			amount,
498			amount_f,
499			token_id,
500			batch_num,
501			eth_block_num,
502			to_forge_l1_txs_num,
503			user_origin,
504			from_eth_addr,
505			from_bjj,
506			load_amount,
507			load_amount_f,
508			fee,
509			nonce
510		) VALUES %s;`,
511		txs[:],
512	)
513}
514
515// // GetTxs returns a list of txs from the DB
516// func (hdb *HistoryDB) GetTxs() ([]common.Tx, error) {
517// 	var txs []*common.Tx
518// 	err := meddler.QueryAll(
519// 		hdb.db, &txs,
520// 		`SELECT * FROM tx
521// 		ORDER BY (batch_num, position) ASC`,
522// 	)
523// 	return db.SlicePtrsToSlice(txs).([]common.Tx), err
524// }
525
526// GetHistoryTx returns a tx from the DB given a TxID
527func (hdb *HistoryDB) GetHistoryTx(txID common.TxID) (*HistoryTx, error) {
528	tx := &HistoryTx{}
529	err := meddler.QueryRow(
530		hdb.db, tx, `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position, 
531		tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd, 
532		tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin, 
533		tx.from_eth_addr, tx.from_bjj, tx.load_amount, 
534		tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
535		token.token_id, token.eth_block_num AS token_block,
536		token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
537		token.usd_update, block.timestamp
538		FROM tx INNER JOIN token ON tx.token_id = token.token_id 
539		INNER JOIN block ON tx.eth_block_num = block.eth_block_num 
540		WHERE tx.id = $1;`, txID,
541	)
542	return tx, err
543}
544
545// GetHistoryTxs returns a list of txs from the DB using the HistoryTx struct
546// and pagination info
547func (hdb *HistoryDB) GetHistoryTxs(
548	ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
549	tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
550	fromItem, limit *uint, order string,
551) ([]HistoryTx, *db.Pagination, error) {
552	if ethAddr != nil && bjj != nil {
553		return nil, nil, errors.New("ethAddr and bjj are incompatible")
554	}
555	var query string
556	var args []interface{}
557	queryStr := `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position, 
558	tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd, 
559	tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin, 
560	tx.from_eth_addr, tx.from_bjj, tx.load_amount, 
561	tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
562	token.token_id, token.eth_block_num AS token_block,
563	token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
564	token.usd_update, block.timestamp, count(*) OVER() AS total_items, 
565	MIN(tx.item_id)  OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item 
566	FROM tx INNER JOIN token ON tx.token_id = token.token_id 
567	INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
568	// Apply filters
569	nextIsAnd := false
570	// ethAddr filter
571	if ethAddr != nil {
572		queryStr = `WITH acc AS 
573		(select idx from account where eth_addr = ?) ` + queryStr
574		queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
575		nextIsAnd = true
576		args = append(args, ethAddr)
577	} else if bjj != nil { // bjj filter
578		queryStr = `WITH acc AS 
579		(select idx from account where bjj = ?) ` + queryStr
580		queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
581		nextIsAnd = true
582		args = append(args, bjj)
583	}
584	// tokenID filter
585	if tokenID != nil {
586		if nextIsAnd {
587			queryStr += "AND "
588		} else {
589			queryStr += "WHERE "
590		}
591		queryStr += "tx.token_id = ? "
592		args = append(args, tokenID)
593		nextIsAnd = true
594	}
595	// idx filter
596	if idx != nil {
597		if nextIsAnd {
598			queryStr += "AND "
599		} else {
600			queryStr += "WHERE "
601		}
602		queryStr += "(tx.from_idx = ? OR tx.to_idx = ?) "
603		args = append(args, idx, idx)
604		nextIsAnd = true
605	}
606	// batchNum filter
607	if batchNum != nil {
608		if nextIsAnd {
609			queryStr += "AND "
610		} else {
611			queryStr += "WHERE "
612		}
613		queryStr += "tx.batch_num = ? "
614		args = append(args, batchNum)
615		nextIsAnd = true
616	}
617	// txType filter
618	if txType != nil {
619		if nextIsAnd {
620			queryStr += "AND "
621		} else {
622			queryStr += "WHERE "
623		}
624		queryStr += "tx.type = ? "
625		args = append(args, txType)
626		nextIsAnd = true
627	}
628	if fromItem != nil {
629		if nextIsAnd {
630			queryStr += "AND "
631		} else {
632			queryStr += "WHERE "
633		}
634		if order == OrderAsc {
635			queryStr += "tx.item_id >= ? "
636		} else {
637			queryStr += "tx.item_id <= ? "
638		}
639		args = append(args, fromItem)
640		nextIsAnd = true
641	}
642	if nextIsAnd {
643		queryStr += "AND "
644	} else {
645		queryStr += "WHERE "
646	}
647	queryStr += "tx.batch_num IS NOT NULL "
648
649	// pagination
650	queryStr += "ORDER BY tx.item_id "
651	if order == OrderAsc {
652		queryStr += " ASC "
653	} else {
654		queryStr += " DESC "
655	}
656	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
657	query = hdb.db.Rebind(queryStr)
658	log.Debug(query)
659	txsPtrs := []*HistoryTx{}
660	if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
661		return nil, nil, err
662	}
663	txs := db.SlicePtrsToSlice(txsPtrs).([]HistoryTx)
664	if len(txs) == 0 {
665		return nil, nil, sql.ErrNoRows
666	}
667	return txs, &db.Pagination{
668		TotalItems: txs[0].TotalItems,
669		FirstItem:  txs[0].FirstItem,
670		LastItem:   txs[0].LastItem,
671	}, nil
672}
673
674// GetExit returns a exit from the DB
675func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) {
676	exit := &HistoryExit{}
677	err := meddler.QueryRow(
678		hdb.db, exit, `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
679		token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
680		FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx 
681		INNER JOIN token ON account.token_id = token.token_id 
682		WHERE exit_tree.batch_num = $1 AND exit_tree.account_idx = $2;`, batchNum, idx,
683	)
684	return exit, err
685}
686
687// GetExits returns a list of exits from the DB and pagination info
688func (hdb *HistoryDB) GetExits(
689	ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
690	tokenID *common.TokenID, idx *common.Idx, batchNum *uint,
691	fromItem, limit *uint, order string,
692) ([]HistoryExit, *db.Pagination, error) {
693	if ethAddr != nil && bjj != nil {
694		return nil, nil, errors.New("ethAddr and bjj are incompatible")
695	}
696	var query string
697	var args []interface{}
698	queryStr := `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
699	token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
700	token.usd_update, COUNT(*) OVER() AS total_items, MIN(exit_tree.item_id) OVER() AS first_item, MAX(exit_tree.item_id) OVER() AS last_item
701	FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx 
702	INNER JOIN token ON account.token_id = token.token_id `
703	// Apply filters
704	nextIsAnd := false
705	// ethAddr filter
706	if ethAddr != nil {
707		queryStr += "WHERE account.eth_addr = ? "
708		nextIsAnd = true
709		args = append(args, ethAddr)
710	} else if bjj != nil { // bjj filter
711		queryStr += "WHERE account.bjj = ? "
712		nextIsAnd = true
713		args = append(args, bjj)
714	}
715	// tokenID filter
716	if tokenID != nil {
717		if nextIsAnd {
718			queryStr += "AND "
719		} else {
720			queryStr += "WHERE "
721		}
722		queryStr += "account.token_id = ? "
723		args = append(args, tokenID)
724		nextIsAnd = true
725	}
726	// idx filter
727	if idx != nil {
728		if nextIsAnd {
729			queryStr += "AND "
730		} else {
731			queryStr += "WHERE "
732		}
733		queryStr += "exit_tree.account_idx = ? "
734		args = append(args, idx)
735		nextIsAnd = true
736	}
737	// batchNum filter
738	if batchNum != nil {
739		if nextIsAnd {
740			queryStr += "AND "
741		} else {
742			queryStr += "WHERE "
743		}
744		queryStr += "exit_tree.batch_num = ? "
745		args = append(args, batchNum)
746		nextIsAnd = true
747	}
748	if fromItem != nil {
749		if nextIsAnd {
750			queryStr += "AND "
751		} else {
752			queryStr += "WHERE "
753		}
754		if order == OrderAsc {
755			queryStr += "exit_tree.item_id >= ? "
756		} else {
757			queryStr += "exit_tree.item_id <= ? "
758		}
759		args = append(args, fromItem)
760		// nextIsAnd = true
761	}
762	// pagination
763	queryStr += "ORDER BY exit_tree.item_id "
764	if order == OrderAsc {
765		queryStr += " ASC "
766	} else {
767		queryStr += " DESC "
768	}
769	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
770	query = hdb.db.Rebind(queryStr)
771	// log.Debug(query)
772	exits := []*HistoryExit{}
773	if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
774		return nil, nil, err
775	}
776	if len(exits) == 0 {
777		return nil, nil, sql.ErrNoRows
778	}
779	return db.SlicePtrsToSlice(exits).([]HistoryExit), &db.Pagination{
780		TotalItems: exits[0].TotalItems,
781		FirstItem:  exits[0].FirstItem,
782		LastItem:   exits[0].LastItem,
783	}, nil
784}
785
786// // GetTx returns a tx from the DB
787// func (hdb *HistoryDB) GetTx(txID common.TxID) (*common.Tx, error) {
788// 	tx := new(common.Tx)
789// 	return tx, meddler.QueryRow(
790// 		hdb.db, tx,
791// 		"SELECT * FROM tx WHERE id = $1;",
792// 		txID,
793// 	)
794// }
795
796// // GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account
797// // TODO: This is currently not used.  Figure out if it should be used somewhere or removed.
798// func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) {
799// 	var txs []*common.Tx
800// 	err := meddler.QueryAll(
801// 		hdb.db, &txs,
802// 		"SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;",
803// 		toForgeL1TxsNum,
804// 	)
805// 	return txs, err
806// }
807
808// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
809
810// GetLastTxsPosition for a given to_forge_l1_txs_num
811func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) {
812	row := hdb.db.QueryRow("SELECT MAX(position) FROM tx WHERE to_forge_l1_txs_num = $1;", toForgeL1TxsNum)
813	var lastL1TxsPosition int
814	return lastL1TxsPosition, row.Scan(&lastL1TxsPosition)
815}
816
817// AddBlockSCData stores all the information of a block retrieved by the Synchronizer
818func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
819	txn, err := hdb.db.Begin()
820	if err != nil {
821		return err
822	}
823	defer func() {
824		if err != nil {
825			errRollback := txn.Rollback()
826			if errRollback != nil {
827				log.Errorw("Rollback", "err", errRollback)
828			}
829		}
830	}()
831
832	// Add block
833	err = hdb.addBlock(txn, blockData.Block)
834	if err != nil {
835		return err
836	}
837
838	// Add Coordinators
839	if len(blockData.Coordinators) > 0 {
840		err = hdb.addCoordinators(txn, blockData.Coordinators)
841		if err != nil {
842			return err
843		}
844	}
845
846	// Add Bids
847	if len(blockData.Bids) > 0 {
848		err = hdb.addBids(txn, blockData.Bids)
849		if err != nil {
850			return err
851		}
852	}
853
854	// Add Tokens
855	if len(blockData.RegisteredTokens) > 0 {
856		err = hdb.addTokens(txn, blockData.RegisteredTokens)
857		if err != nil {
858			return err
859		}
860	}
861
862	// Add l1 Txs
863	if len(blockData.L1UserTxs) > 0 {
864		err = hdb.addL1Txs(txn, blockData.L1UserTxs)
865		if err != nil {
866			return err
867		}
868	}
869
870	// Add Batches
871	for _, batch := range blockData.Batches {
872		// Add Batch: this will trigger an update on the DB
873		// that will set the batch num of forged L1 txs in this batch
874		err = hdb.addBatch(txn, batch.Batch)
875		if err != nil {
876			return err
877		}
878
879		// Add unforged l1 Txs
880		if batch.L1Batch {
881			if len(batch.L1CoordinatorTxs) > 0 {
882				err = hdb.addL1Txs(txn, batch.L1CoordinatorTxs)
883				if err != nil {
884					return err
885				}
886			}
887		}
888
889		// Add l2 Txs
890		if len(batch.L2Txs) > 0 {
891			err = hdb.addL2Txs(txn, batch.L2Txs)
892			if err != nil {
893				return err
894			}
895		}
896
897		// Add accounts
898		if len(batch.CreatedAccounts) > 0 {
899			err = hdb.addAccounts(txn, batch.CreatedAccounts)
900			if err != nil {
901				return err
902			}
903		}
904
905		// Add exit tree
906		if len(batch.ExitTree) > 0 {
907			err = hdb.addExitTree(txn, batch.ExitTree)
908			if err != nil {
909				return err
910			}
911		}
912
913		// TODO: INSERT CONTRACTS VARS
914	}
915
916	return txn.Commit()
917}
918
919// GetCoordinator returns a coordinator by its bidderAddr
920func (hdb *HistoryDB) GetCoordinator(bidderAddr ethCommon.Address) (*HistoryCoordinator, error) {
921	coordinator := &HistoryCoordinator{}
922	err := meddler.QueryRow(
923		hdb.db, coordinator, `SELECT * FROM coordinator WHERE bidder_addr = $1;`, bidderAddr,
924	)
925	return coordinator, err
926}
927
928// GetCoordinators returns a list of coordinators from the DB and pagination info
929func (hdb *HistoryDB) GetCoordinators(fromItem, limit *uint, order string) ([]HistoryCoordinator, *db.Pagination, error) {
930	var query string
931	var args []interface{}
932	queryStr := `SELECT coordinator.*, 
933	COUNT(*) OVER() AS total_items, MIN(coordinator.item_id) OVER() AS first_item, MAX(coordinator.item_id) OVER() AS last_item
934	FROM coordinator `
935	// Apply filters
936	if fromItem != nil {
937		queryStr += "WHERE "
938		if order == OrderAsc {
939			queryStr += "coordinator.item_id >= ? "
940		} else {
941			queryStr += "coordinator.item_id <= ? "
942		}
943		args = append(args, fromItem)
944	}
945	// pagination
946	queryStr += "ORDER BY coordinator.item_id "
947	if order == OrderAsc {
948		queryStr += " ASC "
949	} else {
950		queryStr += " DESC "
951	}
952	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
953	query = hdb.db.Rebind(queryStr)
954
955	coordinators := []*HistoryCoordinator{}
956	if err := meddler.QueryAll(hdb.db, &coordinators, query, args...); err != nil {
957		return nil, nil, err
958	}
959	if len(coordinators) == 0 {
960		return nil, nil, sql.ErrNoRows
961	}
962	return db.SlicePtrsToSlice(coordinators).([]HistoryCoordinator), &db.Pagination{
963		TotalItems: coordinators[0].TotalItems,
964		FirstItem:  coordinators[0].FirstItem,
965		LastItem:   coordinators[0].LastItem,
966	}, nil
967}