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() ([]TokenRead, error) {
302	var tokens []*TokenRead
303	err := meddler.QueryAll(
304		hdb.db, &tokens,
305		"SELECT * FROM token ORDER BY token_id;",
306	)
307	return db.SlicePtrsToSlice(tokens).([]TokenRead), err
308}
309
310// GetTokenSymbols returns all the token symbols from the DB
311func (hdb *HistoryDB) GetTokenSymbols() ([]string, error) {
312	var tokenSymbols []string
313	rows, err := hdb.db.Query("SELECT symbol FROM token;")
314	if err != nil {
315		return nil, err
316	}
317	sym := new(string)
318	for rows.Next() {
319		err = rows.Scan(sym)
320		if err != nil {
321			return nil, err
322		}
323		tokenSymbols = append(tokenSymbols, *sym)
324	}
325	return tokenSymbols, nil
326}
327
328// AddAccounts insert accounts into the DB
329func (hdb *HistoryDB) AddAccounts(accounts []common.Account) error {
330	return hdb.addAccounts(hdb.db, accounts)
331}
332func (hdb *HistoryDB) addAccounts(d meddler.DB, accounts []common.Account) error {
333	return db.BulkInsert(
334		d,
335		`INSERT INTO account (
336			idx,
337			token_id,
338			batch_num,
339			bjj,
340			eth_addr
341		) VALUES %s;`,
342		accounts[:],
343	)
344}
345
346// GetAccounts returns a list of accounts from the DB
347func (hdb *HistoryDB) GetAccounts() ([]common.Account, error) {
348	var accs []*common.Account
349	err := meddler.QueryAll(
350		hdb.db, &accs,
351		"SELECT * FROM account ORDER BY idx;",
352	)
353	return db.SlicePtrsToSlice(accs).([]common.Account), err
354}
355
356// AddL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
357// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
358// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
359func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error { return hdb.addL1Txs(hdb.db, l1txs) }
360
361// addL1Txs inserts L1 txs to the DB. USD and LoadAmountUSD will be set automatically before storing the tx.
362// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
363// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
364func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
365	txs := []txWrite{}
366	for i := 0; i < len(l1txs); i++ {
367		af := new(big.Float).SetInt(l1txs[i].Amount)
368		amountFloat, _ := af.Float64()
369		laf := new(big.Float).SetInt(l1txs[i].LoadAmount)
370		loadAmountFloat, _ := laf.Float64()
371		txs = append(txs, txWrite{
372			// Generic
373			IsL1:        true,
374			TxID:        l1txs[i].TxID,
375			Type:        l1txs[i].Type,
376			Position:    l1txs[i].Position,
377			FromIdx:     &l1txs[i].FromIdx,
378			ToIdx:       l1txs[i].ToIdx,
379			Amount:      l1txs[i].Amount,
380			AmountFloat: amountFloat,
381			TokenID:     l1txs[i].TokenID,
382			BatchNum:    l1txs[i].BatchNum,
383			EthBlockNum: l1txs[i].EthBlockNum,
384			// L1
385			ToForgeL1TxsNum: l1txs[i].ToForgeL1TxsNum,
386			UserOrigin:      &l1txs[i].UserOrigin,
387			FromEthAddr:     &l1txs[i].FromEthAddr,
388			FromBJJ:         l1txs[i].FromBJJ,
389			LoadAmount:      l1txs[i].LoadAmount,
390			LoadAmountFloat: &loadAmountFloat,
391		})
392	}
393	return hdb.addTxs(d, txs)
394}
395
396// AddL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
397func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error { return hdb.addL2Txs(hdb.db, l2txs) }
398
399// addL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
400func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
401	txs := []txWrite{}
402	for i := 0; i < len(l2txs); i++ {
403		f := new(big.Float).SetInt(l2txs[i].Amount)
404		amountFloat, _ := f.Float64()
405		txs = append(txs, txWrite{
406			// Generic
407			IsL1:        false,
408			TxID:        l2txs[i].TxID,
409			Type:        l2txs[i].Type,
410			Position:    l2txs[i].Position,
411			FromIdx:     &l2txs[i].FromIdx,
412			ToIdx:       l2txs[i].ToIdx,
413			Amount:      l2txs[i].Amount,
414			AmountFloat: amountFloat,
415			BatchNum:    &l2txs[i].BatchNum,
416			EthBlockNum: l2txs[i].EthBlockNum,
417			// L2
418			Fee:   &l2txs[i].Fee,
419			Nonce: &l2txs[i].Nonce,
420		})
421	}
422	return hdb.addTxs(d, txs)
423}
424
425func (hdb *HistoryDB) addTxs(d meddler.DB, txs []txWrite) error {
426	return db.BulkInsert(
427		d,
428		`INSERT INTO tx (
429			is_l1,
430			id,
431			type,
432			position,
433			from_idx,
434			to_idx,
435			amount,
436			amount_f,
437			token_id,
438			batch_num,
439			eth_block_num,
440			to_forge_l1_txs_num,
441			user_origin,
442			from_eth_addr,
443			from_bjj,
444			load_amount,
445			load_amount_f,
446			fee,
447			nonce
448		) VALUES %s;`,
449		txs[:],
450	)
451}
452
453// // GetTxs returns a list of txs from the DB
454// func (hdb *HistoryDB) GetTxs() ([]common.Tx, error) {
455// 	var txs []*common.Tx
456// 	err := meddler.QueryAll(
457// 		hdb.db, &txs,
458// 		`SELECT * FROM tx
459// 		ORDER BY (batch_num, position) ASC`,
460// 	)
461// 	return db.SlicePtrsToSlice(txs).([]common.Tx), err
462// }
463
464// GetHistoryTx returns a tx from the DB given a TxID
465func (hdb *HistoryDB) GetHistoryTx(txID common.TxID) (*HistoryTx, error) {
466	tx := &HistoryTx{}
467	err := meddler.QueryRow(
468		hdb.db, tx, `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position, 
469		tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd, 
470		tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin, 
471		tx.from_eth_addr, tx.from_bjj, tx.load_amount, 
472		tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
473		token.token_id, token.eth_block_num AS token_block,
474		token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
475		token.usd_update, block.timestamp
476		FROM tx INNER JOIN token ON tx.token_id = token.token_id 
477		INNER JOIN block ON tx.eth_block_num = block.eth_block_num 
478		WHERE tx.id = $1;`, txID,
479	)
480	return tx, err
481}
482
483// GetHistoryTxs returns a list of txs from the DB using the HistoryTx struct
484// and pagination info
485func (hdb *HistoryDB) GetHistoryTxs(
486	ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
487	tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType,
488	fromItem, limit *uint, order string,
489) ([]HistoryTx, *db.Pagination, error) {
490	if ethAddr != nil && bjj != nil {
491		return nil, nil, errors.New("ethAddr and bjj are incompatible")
492	}
493	var query string
494	var args []interface{}
495	queryStr := `SELECT tx.item_id, tx.is_l1, tx.id, tx.type, tx.position, 
496	tx.from_idx, tx.to_idx, tx.amount, tx.token_id, tx.amount_usd, 
497	tx.batch_num, tx.eth_block_num, tx.to_forge_l1_txs_num, tx.user_origin, 
498	tx.from_eth_addr, tx.from_bjj, tx.load_amount, 
499	tx.load_amount_usd, tx.fee, tx.fee_usd, tx.nonce,
500	token.token_id, token.eth_block_num AS token_block,
501	token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
502	token.usd_update, block.timestamp, count(*) OVER() AS total_items, 
503	MIN(tx.item_id)  OVER() AS first_item, MAX(tx.item_id) OVER() AS last_item 
504	FROM tx INNER JOIN token ON tx.token_id = token.token_id 
505	INNER JOIN block ON tx.eth_block_num = block.eth_block_num `
506	// Apply filters
507	nextIsAnd := false
508	// ethAddr filter
509	if ethAddr != nil {
510		queryStr = `WITH acc AS 
511		(select idx from account where eth_addr = ?) ` + queryStr
512		queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
513		nextIsAnd = true
514		args = append(args, ethAddr)
515	} else if bjj != nil { // bjj filter
516		queryStr = `WITH acc AS 
517		(select idx from account where bjj = ?) ` + queryStr
518		queryStr += ", acc WHERE (tx.from_idx IN(acc.idx) OR tx.to_idx IN(acc.idx)) "
519		nextIsAnd = true
520		args = append(args, bjj)
521	}
522	// tokenID filter
523	if tokenID != nil {
524		if nextIsAnd {
525			queryStr += "AND "
526		} else {
527			queryStr += "WHERE "
528		}
529		queryStr += "tx.token_id = ? "
530		args = append(args, tokenID)
531		nextIsAnd = true
532	}
533	// idx filter
534	if idx != nil {
535		if nextIsAnd {
536			queryStr += "AND "
537		} else {
538			queryStr += "WHERE "
539		}
540		queryStr += "(tx.from_idx = ? OR tx.to_idx = ?) "
541		args = append(args, idx, idx)
542		nextIsAnd = true
543	}
544	// batchNum filter
545	if batchNum != nil {
546		if nextIsAnd {
547			queryStr += "AND "
548		} else {
549			queryStr += "WHERE "
550		}
551		queryStr += "tx.batch_num = ? "
552		args = append(args, batchNum)
553		nextIsAnd = true
554	}
555	// txType filter
556	if txType != nil {
557		if nextIsAnd {
558			queryStr += "AND "
559		} else {
560			queryStr += "WHERE "
561		}
562		queryStr += "tx.type = ? "
563		args = append(args, txType)
564		nextIsAnd = true
565	}
566	if fromItem != nil {
567		if nextIsAnd {
568			queryStr += "AND "
569		} else {
570			queryStr += "WHERE "
571		}
572		if order == OrderAsc {
573			queryStr += "tx.item_id >= ? "
574		} else {
575			queryStr += "tx.item_id <= ? "
576		}
577		args = append(args, fromItem)
578		nextIsAnd = true
579	}
580	if nextIsAnd {
581		queryStr += "AND "
582	} else {
583		queryStr += "WHERE "
584	}
585	queryStr += "tx.batch_num IS NOT NULL "
586
587	// pagination
588	queryStr += "ORDER BY tx.item_id "
589	if order == OrderAsc {
590		queryStr += " ASC "
591	} else {
592		queryStr += " DESC "
593	}
594	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
595	query = hdb.db.Rebind(queryStr)
596	log.Debug(query)
597	txsPtrs := []*HistoryTx{}
598	if err := meddler.QueryAll(hdb.db, &txsPtrs, query, args...); err != nil {
599		return nil, nil, err
600	}
601	txs := db.SlicePtrsToSlice(txsPtrs).([]HistoryTx)
602	if len(txs) == 0 {
603		return nil, nil, sql.ErrNoRows
604	}
605	return txs, &db.Pagination{
606		TotalItems: txs[0].TotalItems,
607		FirstItem:  txs[0].FirstItem,
608		LastItem:   txs[0].LastItem,
609	}, nil
610}
611
612// GetExit returns a exit from the DB
613func (hdb *HistoryDB) GetExit(batchNum *uint, idx *common.Idx) (*HistoryExit, error) {
614	exit := &HistoryExit{}
615	err := meddler.QueryRow(
616		hdb.db, exit, `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
617		token.eth_addr, token.name, token.symbol, token.decimals, token.usd, token.usd_update
618		FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx 
619		INNER JOIN token ON account.token_id = token.token_id 
620		WHERE exit_tree.batch_num = $1 AND exit_tree.account_idx = $2;`, batchNum, idx,
621	)
622	return exit, err
623}
624
625// GetExits returns a list of exits from the DB and pagination info
626func (hdb *HistoryDB) GetExits(
627	ethAddr *ethCommon.Address, bjj *babyjub.PublicKey,
628	tokenID *common.TokenID, idx *common.Idx, batchNum *uint,
629	fromItem, limit *uint, order string,
630) ([]HistoryExit, *db.Pagination, error) {
631	if ethAddr != nil && bjj != nil {
632		return nil, nil, errors.New("ethAddr and bjj are incompatible")
633	}
634	var query string
635	var args []interface{}
636	queryStr := `SELECT exit_tree.*, token.token_id, token.eth_block_num AS token_block,
637	token.eth_addr, token.name, token.symbol, token.decimals, token.usd,
638	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
639	FROM exit_tree INNER JOIN account ON exit_tree.account_idx = account.idx 
640	INNER JOIN token ON account.token_id = token.token_id `
641	// Apply filters
642	nextIsAnd := false
643	// ethAddr filter
644	if ethAddr != nil {
645		queryStr += "WHERE account.eth_addr = ? "
646		nextIsAnd = true
647		args = append(args, ethAddr)
648	} else if bjj != nil { // bjj filter
649		queryStr += "WHERE account.bjj = ? "
650		nextIsAnd = true
651		args = append(args, bjj)
652	}
653	// tokenID filter
654	if tokenID != nil {
655		if nextIsAnd {
656			queryStr += "AND "
657		} else {
658			queryStr += "WHERE "
659		}
660		queryStr += "account.token_id = ? "
661		args = append(args, tokenID)
662		nextIsAnd = true
663	}
664	// idx filter
665	if idx != nil {
666		if nextIsAnd {
667			queryStr += "AND "
668		} else {
669			queryStr += "WHERE "
670		}
671		queryStr += "exit_tree.account_idx = ? "
672		args = append(args, idx)
673		nextIsAnd = true
674	}
675	// batchNum filter
676	if batchNum != nil {
677		if nextIsAnd {
678			queryStr += "AND "
679		} else {
680			queryStr += "WHERE "
681		}
682		queryStr += "exit_tree.batch_num = ? "
683		args = append(args, batchNum)
684		nextIsAnd = true
685	}
686	if fromItem != nil {
687		if nextIsAnd {
688			queryStr += "AND "
689		} else {
690			queryStr += "WHERE "
691		}
692		if order == OrderAsc {
693			queryStr += "exit_tree.item_id >= ? "
694		} else {
695			queryStr += "exit_tree.item_id <= ? "
696		}
697		args = append(args, fromItem)
698		// nextIsAnd = true
699	}
700	// pagination
701	queryStr += "ORDER BY exit_tree.item_id "
702	if order == OrderAsc {
703		queryStr += " ASC "
704	} else {
705		queryStr += " DESC "
706	}
707	queryStr += fmt.Sprintf("LIMIT %d;", *limit)
708	query = hdb.db.Rebind(queryStr)
709	// log.Debug(query)
710	exits := []*HistoryExit{}
711	if err := meddler.QueryAll(hdb.db, &exits, query, args...); err != nil {
712		return nil, nil, err
713	}
714	if len(exits) == 0 {
715		return nil, nil, sql.ErrNoRows
716	}
717	return db.SlicePtrsToSlice(exits).([]HistoryExit), &db.Pagination{
718		TotalItems: exits[0].TotalItems,
719		FirstItem:  exits[0].FirstItem,
720		LastItem:   exits[0].LastItem,
721	}, nil
722}
723
724// // GetTx returns a tx from the DB
725// func (hdb *HistoryDB) GetTx(txID common.TxID) (*common.Tx, error) {
726// 	tx := new(common.Tx)
727// 	return tx, meddler.QueryRow(
728// 		hdb.db, tx,
729// 		"SELECT * FROM tx WHERE id = $1;",
730// 		txID,
731// 	)
732// }
733
734// // GetL1UserTxs gets L1 User Txs to be forged in a batch that will create an account
735// // TODO: This is currently not used.  Figure out if it should be used somewhere or removed.
736// func (hdb *HistoryDB) GetL1UserTxs(toForgeL1TxsNum int64) ([]*common.Tx, error) {
737// 	var txs []*common.Tx
738// 	err := meddler.QueryAll(
739// 		hdb.db, &txs,
740// 		"SELECT * FROM tx WHERE to_forge_l1_txs_num = $1 AND is_l1 = TRUE AND user_origin = TRUE;",
741// 		toForgeL1TxsNum,
742// 	)
743// 	return txs, err
744// }
745
746// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
747
748// GetLastTxsPosition for a given to_forge_l1_txs_num
749func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) {
750	row := hdb.db.QueryRow("SELECT MAX(position) FROM tx WHERE to_forge_l1_txs_num = $1;", toForgeL1TxsNum)
751	var lastL1TxsPosition int
752	return lastL1TxsPosition, row.Scan(&lastL1TxsPosition)
753}
754
755// AddBlockSCData stores all the information of a block retrieved by the Synchronizer
756func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
757	txn, err := hdb.db.Begin()
758	if err != nil {
759		return err
760	}
761	defer func() {
762		if err != nil {
763			errRollback := txn.Rollback()
764			if errRollback != nil {
765				log.Errorw("Rollback", "err", errRollback)
766			}
767		}
768	}()
769
770	// Add block
771	err = hdb.addBlock(txn, blockData.Block)
772	if err != nil {
773		return err
774	}
775
776	// Add Coordinators
777	if len(blockData.Coordinators) > 0 {
778		err = hdb.addCoordinators(txn, blockData.Coordinators)
779		if err != nil {
780			return err
781		}
782	}
783
784	// Add Bids
785	if len(blockData.Bids) > 0 {
786		err = hdb.addBids(txn, blockData.Bids)
787		if err != nil {
788			return err
789		}
790	}
791
792	// Add Tokens
793	if len(blockData.RegisteredTokens) > 0 {
794		err = hdb.addTokens(txn, blockData.RegisteredTokens)
795		if err != nil {
796			return err
797		}
798	}
799
800	// Add l1 Txs
801	if len(blockData.L1UserTxs) > 0 {
802		err = hdb.addL1Txs(txn, blockData.L1UserTxs)
803		if err != nil {
804			return err
805		}
806	}
807
808	// Add Batches
809	for _, batch := range blockData.Batches {
810		// Add Batch: this will trigger an update on the DB
811		// that will set the batch num of forged L1 txs in this batch
812		err = hdb.addBatch(txn, batch.Batch)
813		if err != nil {
814			return err
815		}
816
817		// Add unforged l1 Txs
818		if batch.L1Batch {
819			if len(batch.L1CoordinatorTxs) > 0 {
820				err = hdb.addL1Txs(txn, batch.L1CoordinatorTxs)
821				if err != nil {
822					return err
823				}
824			}
825		}
826
827		// Add l2 Txs
828		if len(batch.L2Txs) > 0 {
829			err = hdb.addL2Txs(txn, batch.L2Txs)
830			if err != nil {
831				return err
832			}
833		}
834
835		// Add accounts
836		if len(batch.CreatedAccounts) > 0 {
837			err = hdb.addAccounts(txn, batch.CreatedAccounts)
838			if err != nil {
839				return err
840			}
841		}
842
843		// Add exit tree
844		if len(batch.ExitTree) > 0 {
845			err = hdb.addExitTree(txn, batch.ExitTree)
846			if err != nil {
847				return err
848			}
849		}
850
851		// TODO: INSERT CONTRACTS VARS
852	}
853
854	return txn.Commit()
855}