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