hermez-node/db/historydb/historydb.go
db/historydb/historydb.goRaw
1/*
2Package historydb is responsible for storing and retrieving the historic data of the Hermez network.
3It's mostly but not exclusively used by the API and the synchronizer.
4
5Apart from the logic defined in this package, it's important to notice that there are some triggers defined in the
6migration files that have to be taken into consideration to understanding the results of some queries. This is especially true
7for reorgs: all the data is directly or indirectly related to a block, this makes handling reorgs as easy as deleting the
8reorged blocks from the block table, and all related items will be dropped in cascade. This is not the only case, in general
9functions defined in this package that get affected somehow by the SQL level defined logic has a special mention on the function description.
10
11Some of the database tooling used in this package such as meddler and migration tools is explained in the db package.
12
13This package is spitted in different files following these ideas:
14- historydb.go: constructor and functions used by packages other than the api.
15- apiqueries.go: functions used by the API, the queries implemented in this functions use a semaphore
16to restrict the maximum concurrent connections to the database.
17- views.go: structs used to retrieve/store data from/to the database. When possible, the common structs are used, however
18most of the time there is no 1:1 relation between the struct fields and the tables of the schema, especially when joining tables.
19In some cases, some of the structs defined in this file also include custom Marshallers to easily match the expected api formats.
20- nodeinfo.go: used to handle the interfaces and structs that allow communication across running in different machines/process but sharing the same database.
21*/
22package historydb
23
24import (
25 "math"
26 "math/big"
27 "strings"
28
29 ethCommon "github.com/ethereum/go-ethereum/common"
30 "github.com/hermeznetwork/hermez-node/common"
31 "github.com/hermeznetwork/hermez-node/db"
32 "github.com/hermeznetwork/tracerr"
33 "github.com/jmoiron/sqlx"
34
35 //nolint:errcheck // driver for postgres DB
36 _ "github.com/lib/pq"
37 "github.com/russross/meddler"
38)
39
40const (
41 // OrderAsc indicates ascending order when using pagination
42 OrderAsc = "ASC"
43 // OrderDesc indicates descending order when using pagination
44 OrderDesc = "DESC"
45)
46
47// TODO(Edu): Document here how HistoryDB is kept consistent
48
49// HistoryDB persist the historic of the rollup
50type HistoryDB struct {
51 dbRead *sqlx.DB
52 dbWrite *sqlx.DB
53 apiConnCon *db.APIConnectionController
54}
55
56// NewHistoryDB initialize the DB
57func NewHistoryDB(dbRead, dbWrite *sqlx.DB, apiConnCon *db.APIConnectionController) *HistoryDB {
58 return &HistoryDB{
59 dbRead: dbRead,
60 dbWrite: dbWrite,
61 apiConnCon: apiConnCon,
62 }
63}
64
65// DB returns a pointer to the L2DB.db. This method should be used only for
66// internal testing purposes.
67func (hdb *HistoryDB) DB() *sqlx.DB {
68 return hdb.dbWrite
69}
70
71// AddBlock insert a block into the DB
72func (hdb *HistoryDB) AddBlock(block *common.Block) error { return hdb.addBlock(hdb.dbWrite, block) }
73func (hdb *HistoryDB) addBlock(d meddler.DB, block *common.Block) error {
74 return tracerr.Wrap(meddler.Insert(d, "block", block))
75}
76
77// AddBlocks inserts blocks into the DB
78func (hdb *HistoryDB) AddBlocks(blocks []common.Block) error {
79 return tracerr.Wrap(hdb.addBlocks(hdb.dbWrite, blocks))
80}
81
82func (hdb *HistoryDB) addBlocks(d meddler.DB, blocks []common.Block) error {
83 return tracerr.Wrap(db.BulkInsert(
84 d,
85 `INSERT INTO block (
86 eth_block_num,
87 timestamp,
88 hash
89 ) VALUES %s;`,
90 blocks,
91 ))
92}
93
94// GetBlock retrieve a block from the DB, given a block number
95func (hdb *HistoryDB) GetBlock(blockNum int64) (*common.Block, error) {
96 block := &common.Block{}
97 err := meddler.QueryRow(
98 hdb.dbRead, block,
99 "SELECT * FROM block WHERE eth_block_num = $1;", blockNum,
100 )
101 return block, tracerr.Wrap(err)
102}
103
104// GetAllBlocks retrieve all blocks from the DB
105func (hdb *HistoryDB) GetAllBlocks() ([]common.Block, error) {
106 var blocks []*common.Block
107 err := meddler.QueryAll(
108 hdb.dbRead, &blocks,
109 "SELECT * FROM block ORDER BY eth_block_num;",
110 )
111 return db.SlicePtrsToSlice(blocks).([]common.Block), tracerr.Wrap(err)
112}
113
114// getBlocks retrieve blocks from the DB, given a range of block numbers defined by from and to
115func (hdb *HistoryDB) getBlocks(from, to int64) ([]common.Block, error) {
116 var blocks []*common.Block
117 err := meddler.QueryAll(
118 hdb.dbRead, &blocks,
119 "SELECT * FROM block WHERE $1 <= eth_block_num AND eth_block_num < $2 ORDER BY eth_block_num;",
120 from, to,
121 )
122 return db.SlicePtrsToSlice(blocks).([]common.Block), tracerr.Wrap(err)
123}
124
125// GetLastBlock retrieve the block with the highest block number from the DB
126func (hdb *HistoryDB) GetLastBlock() (*common.Block, error) {
127 block := &common.Block{}
128 err := meddler.QueryRow(
129 hdb.dbRead, block, "SELECT * FROM block ORDER BY eth_block_num DESC LIMIT 1;",
130 )
131 return block, tracerr.Wrap(err)
132}
133
134// AddBatch insert a Batch into the DB
135func (hdb *HistoryDB) AddBatch(batch *common.Batch) error { return hdb.addBatch(hdb.dbWrite, batch) }
136func (hdb *HistoryDB) addBatch(d meddler.DB, batch *common.Batch) error {
137 // Calculate total collected fees in USD
138 // Get IDs of collected tokens for fees
139 tokenIDs := []common.TokenID{}
140 for id := range batch.CollectedFees {
141 tokenIDs = append(tokenIDs, id)
142 }
143 // Get USD value of the tokens
144 type tokenPrice struct {
145 ID common.TokenID `meddler:"token_id"`
146 USD *float64 `meddler:"usd"`
147 Decimals int `meddler:"decimals"`
148 }
149 var tokenPrices []*tokenPrice
150 if len(tokenIDs) > 0 {
151 query, args, err := sqlx.In(
152 "SELECT token_id, usd, decimals FROM token WHERE token_id IN (?);",
153 tokenIDs,
154 )
155 if err != nil {
156 return tracerr.Wrap(err)
157 }
158 query = hdb.dbWrite.Rebind(query)
159 if err := meddler.QueryAll(
160 hdb.dbWrite, &tokenPrices, query, args...,
161 ); err != nil {
162 return tracerr.Wrap(err)
163 }
164 }
165 // Calculate total collected
166 var total float64
167 for _, tokenPrice := range tokenPrices {
168 if tokenPrice.USD == nil {
169 continue
170 }
171 f := new(big.Float).SetInt(batch.CollectedFees[tokenPrice.ID])
172 amount, _ := f.Float64()
173 total += *tokenPrice.USD * (amount / math.Pow(10, float64(tokenPrice.Decimals))) //nolint decimals have to be ^10
174 }
175 batch.TotalFeesUSD = &total
176 // Insert to DB
177 return tracerr.Wrap(meddler.Insert(d, "batch", batch))
178}
179
180// AddBatches insert Bids into the DB
181func (hdb *HistoryDB) AddBatches(batches []common.Batch) error {
182 return tracerr.Wrap(hdb.addBatches(hdb.dbWrite, batches))
183}
184func (hdb *HistoryDB) addBatches(d meddler.DB, batches []common.Batch) error {
185 for i := 0; i < len(batches); i++ {
186 if err := hdb.addBatch(d, &batches[i]); err != nil {
187 return tracerr.Wrap(err)
188 }
189 }
190 return nil
191}
192
193// GetBatch returns the batch with the given batchNum
194func (hdb *HistoryDB) GetBatch(batchNum common.BatchNum) (*common.Batch, error) {
195 var batch common.Batch
196 err := meddler.QueryRow(
197 hdb.dbRead, &batch, `SELECT batch.batch_num, batch.eth_block_num, batch.forger_addr,
198 batch.fees_collected, batch.fee_idxs_coordinator, batch.state_root,
199 batch.num_accounts, batch.last_idx, batch.exit_root, batch.forge_l1_txs_num,
200 batch.slot_num, batch.total_fees_usd FROM batch WHERE batch_num = $1;`,
201 batchNum,
202 )
203 return &batch, tracerr.Wrap(err)
204}
205
206// GetAllBatches retrieve all batches from the DB
207func (hdb *HistoryDB) GetAllBatches() ([]common.Batch, error) {
208 var batches []*common.Batch
209 err := meddler.QueryAll(
210 hdb.dbRead, &batches,
211 `SELECT batch.batch_num, batch.eth_block_num, batch.forger_addr, batch.fees_collected,
212 batch.fee_idxs_coordinator, batch.state_root, batch.num_accounts, batch.last_idx, batch.exit_root,
213 batch.forge_l1_txs_num, batch.slot_num, batch.total_fees_usd FROM batch
214 ORDER BY item_id;`,
215 )
216 return db.SlicePtrsToSlice(batches).([]common.Batch), tracerr.Wrap(err)
217}
218
219// GetBatches retrieve batches from the DB, given a range of batch numbers defined by from and to
220func (hdb *HistoryDB) GetBatches(from, to common.BatchNum) ([]common.Batch, error) {
221 var batches []*common.Batch
222 err := meddler.QueryAll(
223 hdb.dbRead, &batches,
224 `SELECT batch_num, eth_block_num, forger_addr, fees_collected, fee_idxs_coordinator,
225 state_root, num_accounts, last_idx, exit_root, forge_l1_txs_num, slot_num, total_fees_usd
226 FROM batch WHERE $1 <= batch_num AND batch_num < $2 ORDER BY batch_num;`,
227 from, to,
228 )
229 return db.SlicePtrsToSlice(batches).([]common.Batch), tracerr.Wrap(err)
230}
231
232// GetFirstBatchBlockNumBySlot returns the ethereum block number of the first
233// batch within a slot
234func (hdb *HistoryDB) GetFirstBatchBlockNumBySlot(slotNum int64) (int64, error) {
235 row := hdb.dbRead.QueryRow(
236 `SELECT eth_block_num FROM batch
237 WHERE slot_num = $1 ORDER BY batch_num ASC LIMIT 1;`, slotNum,
238 )
239 var blockNum int64
240 return blockNum, tracerr.Wrap(row.Scan(&blockNum))
241}
242
243// GetLastBatchNum returns the BatchNum of the latest forged batch
244func (hdb *HistoryDB) GetLastBatchNum() (common.BatchNum, error) {
245 row := hdb.dbRead.QueryRow("SELECT batch_num FROM batch ORDER BY batch_num DESC LIMIT 1;")
246 var batchNum common.BatchNum
247 return batchNum, tracerr.Wrap(row.Scan(&batchNum))
248}
249
250// GetLastBatch returns the last forged batch
251func (hdb *HistoryDB) GetLastBatch() (*common.Batch, error) {
252 var batch common.Batch
253 err := meddler.QueryRow(
254 hdb.dbRead, &batch, `SELECT batch.batch_num, batch.eth_block_num, batch.forger_addr,
255 batch.fees_collected, batch.fee_idxs_coordinator, batch.state_root,
256 batch.num_accounts, batch.last_idx, batch.exit_root, batch.forge_l1_txs_num,
257 batch.slot_num, batch.total_fees_usd FROM batch ORDER BY batch_num DESC LIMIT 1;`,
258 )
259 return &batch, tracerr.Wrap(err)
260}
261
262// GetLastL1BatchBlockNum returns the blockNum of the latest forged l1Batch
263func (hdb *HistoryDB) GetLastL1BatchBlockNum() (int64, error) {
264 row := hdb.dbRead.QueryRow(`SELECT eth_block_num FROM batch
265 WHERE forge_l1_txs_num IS NOT NULL
266 ORDER BY batch_num DESC LIMIT 1;`)
267 var blockNum int64
268 return blockNum, tracerr.Wrap(row.Scan(&blockNum))
269}
270
271// GetLastL1TxsNum returns the greatest ForgeL1TxsNum in the DB from forged
272// batches. If there's no batch in the DB (nil, nil) is returned.
273func (hdb *HistoryDB) GetLastL1TxsNum() (*int64, error) {
274 row := hdb.dbRead.QueryRow("SELECT MAX(forge_l1_txs_num) FROM batch;")
275 lastL1TxsNum := new(int64)
276 return lastL1TxsNum, tracerr.Wrap(row.Scan(&lastL1TxsNum))
277}
278
279// Reorg deletes all the information that was added into the DB after the
280// lastValidBlock. If lastValidBlock is negative, all block information is
281// deleted.
282func (hdb *HistoryDB) Reorg(lastValidBlock int64) error {
283 var err error
284 if lastValidBlock < 0 {
285 _, err = hdb.dbWrite.Exec("DELETE FROM block;")
286 } else {
287 _, err = hdb.dbWrite.Exec("DELETE FROM block WHERE eth_block_num > $1;", lastValidBlock)
288 }
289 return tracerr.Wrap(err)
290}
291
292// AddBids insert Bids into the DB
293func (hdb *HistoryDB) AddBids(bids []common.Bid) error { return hdb.addBids(hdb.dbWrite, bids) }
294func (hdb *HistoryDB) addBids(d meddler.DB, bids []common.Bid) error {
295 if len(bids) == 0 {
296 return nil
297 }
298 // TODO: check the coordinator info
299 return tracerr.Wrap(db.BulkInsert(
300 d,
301 "INSERT INTO bid (slot_num, bid_value, eth_block_num, bidder_addr) VALUES %s;",
302 bids,
303 ))
304}
305
306// GetAllBids retrieve all bids from the DB
307func (hdb *HistoryDB) GetAllBids() ([]common.Bid, error) {
308 var bids []*common.Bid
309 err := meddler.QueryAll(
310 hdb.dbRead, &bids,
311 `SELECT bid.slot_num, bid.bid_value, bid.eth_block_num, bid.bidder_addr FROM bid
312 ORDER BY item_id;`,
313 )
314 return db.SlicePtrsToSlice(bids).([]common.Bid), tracerr.Wrap(err)
315}
316
317// GetBestBidCoordinator returns the forger address of the highest bidder in a slot by slotNum
318func (hdb *HistoryDB) GetBestBidCoordinator(slotNum int64) (*common.BidCoordinator, error) {
319 bidCoord := &common.BidCoordinator{}
320 err := meddler.QueryRow(
321 hdb.dbRead, bidCoord,
322 `SELECT (
323 SELECT default_slot_set_bid
324 FROM auction_vars
325 WHERE default_slot_set_bid_slot_num <= $1
326 ORDER BY eth_block_num DESC LIMIT 1
327 ),
328 bid.slot_num, bid.bid_value, bid.bidder_addr,
329 coordinator.forger_addr, coordinator.url
330 FROM bid
331 INNER JOIN (
332 SELECT bidder_addr, MAX(item_id) AS item_id FROM coordinator
333 GROUP BY bidder_addr
334 ) c ON bid.bidder_addr = c.bidder_addr
335 INNER JOIN coordinator ON c.item_id = coordinator.item_id
336 WHERE bid.slot_num = $1 ORDER BY bid.item_id DESC LIMIT 1;`,
337 slotNum)
338
339 return bidCoord, tracerr.Wrap(err)
340}
341
342// AddCoordinators insert Coordinators into the DB
343func (hdb *HistoryDB) AddCoordinators(coordinators []common.Coordinator) error {
344 return tracerr.Wrap(hdb.addCoordinators(hdb.dbWrite, coordinators))
345}
346func (hdb *HistoryDB) addCoordinators(d meddler.DB, coordinators []common.Coordinator) error {
347 if len(coordinators) == 0 {
348 return nil
349 }
350 return tracerr.Wrap(db.BulkInsert(
351 d,
352 "INSERT INTO coordinator (bidder_addr, forger_addr, eth_block_num, url) VALUES %s;",
353 coordinators,
354 ))
355}
356
357// AddExitTree insert Exit tree into the DB
358func (hdb *HistoryDB) AddExitTree(exitTree []common.ExitInfo) error {
359 return tracerr.Wrap(hdb.addExitTree(hdb.dbWrite, exitTree))
360}
361func (hdb *HistoryDB) addExitTree(d meddler.DB, exitTree []common.ExitInfo) error {
362 if len(exitTree) == 0 {
363 return nil
364 }
365 return tracerr.Wrap(db.BulkInsert(
366 d,
367 "INSERT INTO exit_tree (batch_num, account_idx, merkle_proof, balance, "+
368 "instant_withdrawn, delayed_withdraw_request, delayed_withdrawn) VALUES %s;",
369 exitTree,
370 ))
371}
372
373func (hdb *HistoryDB) updateExitTree(d sqlx.Ext, blockNum int64,
374 rollupWithdrawals []common.WithdrawInfo, wDelayerWithdrawals []common.WDelayerTransfer) error {
375 if len(rollupWithdrawals) == 0 && len(wDelayerWithdrawals) == 0 {
376 return nil
377 }
378 type withdrawal struct {
379 BatchNum int64 `db:"batch_num"`
380 AccountIdx int64 `db:"account_idx"`
381 InstantWithdrawn *int64 `db:"instant_withdrawn"`
382 DelayedWithdrawRequest *int64 `db:"delayed_withdraw_request"`
383 DelayedWithdrawn *int64 `db:"delayed_withdrawn"`
384 Owner *ethCommon.Address `db:"owner"`
385 Token *ethCommon.Address `db:"token"`
386 }
387 withdrawals := make([]withdrawal, len(rollupWithdrawals)+len(wDelayerWithdrawals))
388 for i := range rollupWithdrawals {
389 info := &rollupWithdrawals[i]
390 withdrawals[i] = withdrawal{
391 BatchNum: int64(info.NumExitRoot),
392 AccountIdx: int64(info.Idx),
393 }
394 if info.InstantWithdraw {
395 withdrawals[i].InstantWithdrawn = &blockNum
396 } else {
397 withdrawals[i].DelayedWithdrawRequest = &blockNum
398 withdrawals[i].Owner = &info.Owner
399 withdrawals[i].Token = &info.Token
400 }
401 }
402 for i := range wDelayerWithdrawals {
403 info := &wDelayerWithdrawals[i]
404 withdrawals[len(rollupWithdrawals)+i] = withdrawal{
405 DelayedWithdrawn: &blockNum,
406 Owner: &info.Owner,
407 Token: &info.Token,
408 }
409 }
410 // In VALUES we set an initial row of NULLs to set the types of each
411 // variable passed as argument
412 const query string = `
413 UPDATE exit_tree e SET
414 instant_withdrawn = d.instant_withdrawn,
415 delayed_withdraw_request = CASE
416 WHEN e.delayed_withdraw_request IS NOT NULL THEN e.delayed_withdraw_request
417 ELSE d.delayed_withdraw_request
418 END,
419 delayed_withdrawn = d.delayed_withdrawn,
420 owner = d.owner,
421 token = d.token
422 FROM (VALUES
423 (NULL::::BIGINT, NULL::::BIGINT, NULL::::BIGINT, NULL::::BIGINT, NULL::::BIGINT, NULL::::BYTEA, NULL::::BYTEA),
424 (:batch_num,
425 :account_idx,
426 :instant_withdrawn,
427 :delayed_withdraw_request,
428 :delayed_withdrawn,
429 :owner,
430 :token)
431 ) as d (batch_num, account_idx, instant_withdrawn, delayed_withdraw_request, delayed_withdrawn, owner, token)
432 WHERE
433 (d.batch_num IS NOT NULL AND e.batch_num = d.batch_num AND e.account_idx = d.account_idx) OR
434 (d.delayed_withdrawn IS NOT NULL AND e.delayed_withdrawn IS NULL AND e.owner = d.owner AND e.token = d.token);
435 `
436 if len(withdrawals) > 0 {
437 if _, err := sqlx.NamedExec(d, query, withdrawals); err != nil {
438 return tracerr.Wrap(err)
439 }
440 }
441
442 return nil
443}
444
445// AddToken insert a token into the DB
446func (hdb *HistoryDB) AddToken(token *common.Token) error {
447 return tracerr.Wrap(meddler.Insert(hdb.dbWrite, "token", token))
448}
449
450// AddTokens insert tokens into the DB
451func (hdb *HistoryDB) AddTokens(tokens []common.Token) error {
452 return hdb.addTokens(hdb.dbWrite, tokens)
453}
454func (hdb *HistoryDB) addTokens(d meddler.DB, tokens []common.Token) error {
455 if len(tokens) == 0 {
456 return nil
457 }
458 // Sanitize name and symbol
459 for i, token := range tokens {
460 token.Name = strings.ToValidUTF8(token.Name, " ")
461 token.Symbol = strings.ToValidUTF8(token.Symbol, " ")
462 tokens[i] = token
463 }
464 return tracerr.Wrap(db.BulkInsert(
465 d,
466 `INSERT INTO token (
467 token_id,
468 eth_block_num,
469 eth_addr,
470 name,
471 symbol,
472 decimals
473 ) VALUES %s;`,
474 tokens,
475 ))
476}
477
478// UpdateTokenValue updates the USD value of a token. Value is the price in
479// USD of a normalized token (1 token = 10^decimals units)
480func (hdb *HistoryDB) UpdateTokenValue(tokenAddr ethCommon.Address, value float64) error {
481 _, err := hdb.dbWrite.Exec(
482 "UPDATE token SET usd = $1 WHERE eth_addr = $2;",
483 value, tokenAddr,
484 )
485 return tracerr.Wrap(err)
486}
487
488// GetToken returns a token from the DB given a TokenID
489func (hdb *HistoryDB) GetToken(tokenID common.TokenID) (*TokenWithUSD, error) {
490 token := &TokenWithUSD{}
491 err := meddler.QueryRow(
492 hdb.dbRead, token, `SELECT * FROM token WHERE token_id = $1;`, tokenID,
493 )
494 return token, tracerr.Wrap(err)
495}
496
497// GetAllTokens returns all tokens from the DB
498func (hdb *HistoryDB) GetAllTokens() ([]TokenWithUSD, error) {
499 var tokens []*TokenWithUSD
500 err := meddler.QueryAll(
501 hdb.dbRead, &tokens,
502 "SELECT * FROM token ORDER BY token_id;",
503 )
504 return db.SlicePtrsToSlice(tokens).([]TokenWithUSD), tracerr.Wrap(err)
505}
506
507// GetTokenSymbolsAndAddrs returns all the token symbols and addresses from the DB
508func (hdb *HistoryDB) GetTokenSymbolsAndAddrs() ([]TokenSymbolAndAddr, error) {
509 var tokens []*TokenSymbolAndAddr
510 err := meddler.QueryAll(
511 hdb.dbRead, &tokens,
512 "SELECT symbol, eth_addr FROM token;",
513 )
514 return db.SlicePtrsToSlice(tokens).([]TokenSymbolAndAddr), tracerr.Wrap(err)
515}
516
517// AddAccounts insert accounts into the DB
518func (hdb *HistoryDB) AddAccounts(accounts []common.Account) error {
519 return tracerr.Wrap(hdb.addAccounts(hdb.dbWrite, accounts))
520}
521func (hdb *HistoryDB) addAccounts(d meddler.DB, accounts []common.Account) error {
522 if len(accounts) == 0 {
523 return nil
524 }
525 return tracerr.Wrap(db.BulkInsert(
526 d,
527 `INSERT INTO account (
528 idx,
529 token_id,
530 batch_num,
531 bjj,
532 eth_addr
533 ) VALUES %s;`,
534 accounts,
535 ))
536}
537
538// GetAllAccounts returns a list of accounts from the DB
539func (hdb *HistoryDB) GetAllAccounts() ([]common.Account, error) {
540 var accs []*common.Account
541 err := meddler.QueryAll(
542 hdb.dbRead, &accs,
543 "SELECT idx, token_id, batch_num, bjj, eth_addr FROM account ORDER BY idx;",
544 )
545 return db.SlicePtrsToSlice(accs).([]common.Account), tracerr.Wrap(err)
546}
547
548// AddAccountUpdates inserts accUpdates into the DB
549func (hdb *HistoryDB) AddAccountUpdates(accUpdates []common.AccountUpdate) error {
550 return tracerr.Wrap(hdb.addAccountUpdates(hdb.dbWrite, accUpdates))
551}
552func (hdb *HistoryDB) addAccountUpdates(d meddler.DB, accUpdates []common.AccountUpdate) error {
553 if len(accUpdates) == 0 {
554 return nil
555 }
556 return tracerr.Wrap(db.BulkInsert(
557 d,
558 `INSERT INTO account_update (
559 eth_block_num,
560 batch_num,
561 idx,
562 nonce,
563 balance
564 ) VALUES %s;`,
565 accUpdates,
566 ))
567}
568
569// GetAllAccountUpdates returns all the AccountUpdate from the DB
570func (hdb *HistoryDB) GetAllAccountUpdates() ([]common.AccountUpdate, error) {
571 var accUpdates []*common.AccountUpdate
572 err := meddler.QueryAll(
573 hdb.dbRead, &accUpdates,
574 "SELECT eth_block_num, batch_num, idx, nonce, balance FROM account_update ORDER BY idx;",
575 )
576 return db.SlicePtrsToSlice(accUpdates).([]common.AccountUpdate), tracerr.Wrap(err)
577}
578
579// AddL1Txs inserts L1 txs to the DB. USD and DepositAmountUSD will be set automatically before storing the tx.
580// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
581// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
582// EffectiveAmount and EffectiveDepositAmount are seted with default values by the DB.
583func (hdb *HistoryDB) AddL1Txs(l1txs []common.L1Tx) error {
584 return tracerr.Wrap(hdb.addL1Txs(hdb.dbWrite, l1txs))
585}
586
587// addL1Txs inserts L1 txs to the DB. USD and DepositAmountUSD will be set automatically before storing the tx.
588// If the tx is originated by a coordinator, BatchNum must be provided. If it's originated by a user,
589// BatchNum should be null, and the value will be setted by a trigger when a batch forges the tx.
590// EffectiveAmount and EffectiveDepositAmount are seted with default values by the DB.
591func (hdb *HistoryDB) addL1Txs(d meddler.DB, l1txs []common.L1Tx) error {
592 if len(l1txs) == 0 {
593 return nil
594 }
595 txs := []txWrite{}
596 for i := 0; i < len(l1txs); i++ {
597 af := new(big.Float).SetInt(l1txs[i].Amount)
598 amountFloat, _ := af.Float64()
599 laf := new(big.Float).SetInt(l1txs[i].DepositAmount)
600 depositAmountFloat, _ := laf.Float64()
601 var effectiveFromIdx *common.Idx
602 if l1txs[i].UserOrigin {
603 if l1txs[i].Type != common.TxTypeCreateAccountDeposit &&
604 l1txs[i].Type != common.TxTypeCreateAccountDepositTransfer {
605 effectiveFromIdx = &l1txs[i].FromIdx
606 }
607 } else {
608 effectiveFromIdx = &l1txs[i].EffectiveFromIdx
609 }
610 txs = append(txs, txWrite{
611 // Generic
612 IsL1: true,
613 TxID: l1txs[i].TxID,
614 Type: l1txs[i].Type,
615 Position: l1txs[i].Position,
616 FromIdx: &l1txs[i].FromIdx,
617 EffectiveFromIdx: effectiveFromIdx,
618 ToIdx: l1txs[i].ToIdx,
619 Amount: l1txs[i].Amount,
620 AmountFloat: amountFloat,
621 TokenID: l1txs[i].TokenID,
622 BatchNum: l1txs[i].BatchNum,
623 EthBlockNum: l1txs[i].EthBlockNum,
624 // L1
625 ToForgeL1TxsNum: l1txs[i].ToForgeL1TxsNum,
626 UserOrigin: &l1txs[i].UserOrigin,
627 FromEthAddr: &l1txs[i].FromEthAddr,
628 FromBJJ: &l1txs[i].FromBJJ,
629 DepositAmount: l1txs[i].DepositAmount,
630 DepositAmountFloat: &depositAmountFloat,
631 })
632 }
633 return tracerr.Wrap(hdb.addTxs(d, txs))
634}
635
636// AddL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
637func (hdb *HistoryDB) AddL2Txs(l2txs []common.L2Tx) error {
638 return tracerr.Wrap(hdb.addL2Txs(hdb.dbWrite, l2txs))
639}
640
641// addL2Txs inserts L2 txs to the DB. TokenID, USD and FeeUSD will be set automatically before storing the tx.
642func (hdb *HistoryDB) addL2Txs(d meddler.DB, l2txs []common.L2Tx) error {
643 txs := []txWrite{}
644 for i := 0; i < len(l2txs); i++ {
645 f := new(big.Float).SetInt(l2txs[i].Amount)
646 amountFloat, _ := f.Float64()
647 txs = append(txs, txWrite{
648 // Generic
649 IsL1: false,
650 TxID: l2txs[i].TxID,
651 Type: l2txs[i].Type,
652 Position: l2txs[i].Position,
653 FromIdx: &l2txs[i].FromIdx,
654 EffectiveFromIdx: &l2txs[i].FromIdx,
655 ToIdx: l2txs[i].ToIdx,
656 TokenID: l2txs[i].TokenID,
657 Amount: l2txs[i].Amount,
658 AmountFloat: amountFloat,
659 BatchNum: &l2txs[i].BatchNum,
660 EthBlockNum: l2txs[i].EthBlockNum,
661 // L2
662 Fee: &l2txs[i].Fee,
663 Nonce: &l2txs[i].Nonce,
664 })
665 }
666 return tracerr.Wrap(hdb.addTxs(d, txs))
667}
668
669func (hdb *HistoryDB) addTxs(d meddler.DB, txs []txWrite) error {
670 if len(txs) == 0 {
671 return nil
672 }
673 return tracerr.Wrap(db.BulkInsert(
674 d,
675 `INSERT INTO tx (
676 is_l1,
677 id,
678 type,
679 position,
680 from_idx,
681 effective_from_idx,
682 to_idx,
683 amount,
684 amount_f,
685 token_id,
686 batch_num,
687 eth_block_num,
688 to_forge_l1_txs_num,
689 user_origin,
690 from_eth_addr,
691 from_bjj,
692 deposit_amount,
693 deposit_amount_f,
694 fee,
695 nonce
696 ) VALUES %s;`,
697 txs,
698 ))
699}
700
701// GetAllExits returns all exit from the DB
702func (hdb *HistoryDB) GetAllExits() ([]common.ExitInfo, error) {
703 var exits []*common.ExitInfo
704 err := meddler.QueryAll(
705 hdb.dbRead, &exits,
706 `SELECT exit_tree.batch_num, exit_tree.account_idx, exit_tree.merkle_proof,
707 exit_tree.balance, exit_tree.instant_withdrawn, exit_tree.delayed_withdraw_request,
708 exit_tree.delayed_withdrawn FROM exit_tree ORDER BY item_id;`,
709 )
710 return db.SlicePtrsToSlice(exits).([]common.ExitInfo), tracerr.Wrap(err)
711}
712
713// GetAllL1UserTxs returns all L1UserTxs from the DB
714func (hdb *HistoryDB) GetAllL1UserTxs() ([]common.L1Tx, error) {
715 var txs []*common.L1Tx
716 err := meddler.QueryAll(
717 hdb.dbRead, &txs,
718 `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin,
719 tx.from_idx, tx.effective_from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id,
720 tx.amount, (CASE WHEN tx.batch_num IS NULL THEN NULL WHEN tx.amount_success THEN tx.amount ELSE 0 END) AS effective_amount,
721 tx.deposit_amount, (CASE WHEN tx.batch_num IS NULL THEN NULL WHEN tx.deposit_amount_success THEN tx.deposit_amount ELSE 0 END) AS effective_deposit_amount,
722 tx.eth_block_num, tx.type, tx.batch_num
723 FROM tx WHERE is_l1 = TRUE AND user_origin = TRUE ORDER BY item_id;`,
724 )
725 return db.SlicePtrsToSlice(txs).([]common.L1Tx), tracerr.Wrap(err)
726}
727
728// GetAllL1CoordinatorTxs returns all L1CoordinatorTxs from the DB
729func (hdb *HistoryDB) GetAllL1CoordinatorTxs() ([]common.L1Tx, error) {
730 var txs []*common.L1Tx
731 // Since the query specifies that only coordinator txs are returned, it's safe to assume
732 // that returned txs will always have effective amounts
733 err := meddler.QueryAll(
734 hdb.dbRead, &txs,
735 `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin,
736 tx.from_idx, tx.effective_from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id,
737 tx.amount, tx.amount AS effective_amount,
738 tx.deposit_amount, tx.deposit_amount AS effective_deposit_amount,
739 tx.eth_block_num, tx.type, tx.batch_num
740 FROM tx WHERE is_l1 = TRUE AND user_origin = FALSE ORDER BY item_id;`,
741 )
742 return db.SlicePtrsToSlice(txs).([]common.L1Tx), tracerr.Wrap(err)
743}
744
745// GetAllL2Txs returns all L2Txs from the DB
746func (hdb *HistoryDB) GetAllL2Txs() ([]common.L2Tx, error) {
747 var txs []*common.L2Tx
748 err := meddler.QueryAll(
749 hdb.dbRead, &txs,
750 `SELECT tx.id, tx.batch_num, tx.position,
751 tx.from_idx, tx.to_idx, tx.amount, tx.token_id,
752 tx.fee, tx.nonce, tx.type, tx.eth_block_num
753 FROM tx WHERE is_l1 = FALSE ORDER BY item_id;`,
754 )
755 return db.SlicePtrsToSlice(txs).([]common.L2Tx), tracerr.Wrap(err)
756}
757
758// GetUnforgedL1UserTxs gets L1 User Txs to be forged in the L1Batch with toForgeL1TxsNum.
759func (hdb *HistoryDB) GetUnforgedL1UserTxs(toForgeL1TxsNum int64) ([]common.L1Tx, error) {
760 var txs []*common.L1Tx
761 err := meddler.QueryAll(
762 hdb.dbRead, &txs, // only L1 user txs can have batch_num set to null
763 `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin,
764 tx.from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id,
765 tx.amount, NULL AS effective_amount,
766 tx.deposit_amount, NULL AS effective_deposit_amount,
767 tx.eth_block_num, tx.type, tx.batch_num
768 FROM tx WHERE batch_num IS NULL AND to_forge_l1_txs_num = $1
769 ORDER BY position;`,
770 toForgeL1TxsNum,
771 )
772 return db.SlicePtrsToSlice(txs).([]common.L1Tx), tracerr.Wrap(err)
773}
774
775// GetUnforgedL1UserTxsCount returns the count of unforged L1Txs (either in
776// open or frozen queues that are not yet forged)
777func (hdb *HistoryDB) GetUnforgedL1UserTxsCount() (int, error) {
778 row := hdb.dbRead.QueryRow(
779 `SELECT COUNT(*) FROM tx WHERE batch_num IS NULL;`,
780 )
781 var count int
782 return count, tracerr.Wrap(row.Scan(&count))
783}
784
785// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
786
787// GetLastTxsPosition for a given to_forge_l1_txs_num
788func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) {
789 row := hdb.dbRead.QueryRow(
790 "SELECT position FROM tx WHERE to_forge_l1_txs_num = $1 ORDER BY position DESC;",
791 toForgeL1TxsNum,
792 )
793 var lastL1TxsPosition int
794 return lastL1TxsPosition, tracerr.Wrap(row.Scan(&lastL1TxsPosition))
795}
796
797// GetSCVars returns the rollup, auction and wdelayer smart contracts variables at their last update.
798func (hdb *HistoryDB) GetSCVars() (*common.RollupVariables, *common.AuctionVariables,
799 *common.WDelayerVariables, error) {
800 var rollup common.RollupVariables
801 var auction common.AuctionVariables
802 var wDelayer common.WDelayerVariables
803 if err := meddler.QueryRow(hdb.dbRead, &rollup,
804 "SELECT * FROM rollup_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
805 return nil, nil, nil, tracerr.Wrap(err)
806 }
807 if err := meddler.QueryRow(hdb.dbRead, &auction,
808 "SELECT * FROM auction_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
809 return nil, nil, nil, tracerr.Wrap(err)
810 }
811 if err := meddler.QueryRow(hdb.dbRead, &wDelayer,
812 "SELECT * FROM wdelayer_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
813 return nil, nil, nil, tracerr.Wrap(err)
814 }
815 return &rollup, &auction, &wDelayer, nil
816}
817
818func (hdb *HistoryDB) setRollupVars(d meddler.DB, rollup *common.RollupVariables) error {
819 return tracerr.Wrap(meddler.Insert(d, "rollup_vars", rollup))
820}
821
822func (hdb *HistoryDB) setAuctionVars(d meddler.DB, auction *common.AuctionVariables) error {
823 return tracerr.Wrap(meddler.Insert(d, "auction_vars", auction))
824}
825
826func (hdb *HistoryDB) setWDelayerVars(d meddler.DB, wDelayer *common.WDelayerVariables) error {
827 return tracerr.Wrap(meddler.Insert(d, "wdelayer_vars", wDelayer))
828}
829
830func (hdb *HistoryDB) addBucketUpdates(d meddler.DB, bucketUpdates []common.BucketUpdate) error {
831 if len(bucketUpdates) == 0 {
832 return nil
833 }
834 return tracerr.Wrap(db.BulkInsert(
835 d,
836 `INSERT INTO bucket_update (
837 eth_block_num,
838 num_bucket,
839 block_stamp,
840 withdrawals
841 ) VALUES %s;`,
842 bucketUpdates,
843 ))
844}
845
846// AddBucketUpdatesTest allows call to unexported method
847// only for internal testing purposes
848func (hdb *HistoryDB) AddBucketUpdatesTest(d meddler.DB, bucketUpdates []common.BucketUpdate) error {
849 return hdb.addBucketUpdates(d, bucketUpdates)
850}
851
852// GetAllBucketUpdates retrieves all the bucket updates
853func (hdb *HistoryDB) GetAllBucketUpdates() ([]common.BucketUpdate, error) {
854 var bucketUpdates []*common.BucketUpdate
855 err := meddler.QueryAll(
856 hdb.dbRead, &bucketUpdates,
857 `SELECT eth_block_num, num_bucket, block_stamp, withdrawals
858 FROM bucket_update ORDER BY item_id;`,
859 )
860 return db.SlicePtrsToSlice(bucketUpdates).([]common.BucketUpdate), tracerr.Wrap(err)
861}
862
863func (hdb *HistoryDB) getMinBidInfo(d meddler.DB,
864 currentSlot, lastClosedSlot int64) ([]MinBidInfo, error) {
865 minBidInfo := []*MinBidInfo{}
866 query := `
867 SELECT DISTINCT default_slot_set_bid, default_slot_set_bid_slot_num FROM auction_vars
868 WHERE default_slot_set_bid_slot_num < $1
869 ORDER BY default_slot_set_bid_slot_num DESC
870 LIMIT $2;`
871 err := meddler.QueryAll(d, &minBidInfo, query, lastClosedSlot, int(lastClosedSlot-currentSlot)+1)
872 return db.SlicePtrsToSlice(minBidInfo).([]MinBidInfo), tracerr.Wrap(err)
873}
874
875func (hdb *HistoryDB) addTokenExchanges(d meddler.DB, tokenExchanges []common.TokenExchange) error {
876 if len(tokenExchanges) == 0 {
877 return nil
878 }
879 return tracerr.Wrap(db.BulkInsert(
880 d,
881 `INSERT INTO token_exchange (
882 eth_block_num,
883 eth_addr,
884 value_usd
885 ) VALUES %s;`,
886 tokenExchanges,
887 ))
888}
889
890// GetAllTokenExchanges retrieves all the token exchanges
891func (hdb *HistoryDB) GetAllTokenExchanges() ([]common.TokenExchange, error) {
892 var tokenExchanges []*common.TokenExchange
893 err := meddler.QueryAll(
894 hdb.dbRead, &tokenExchanges,
895 "SELECT eth_block_num, eth_addr, value_usd FROM token_exchange ORDER BY item_id;",
896 )
897 return db.SlicePtrsToSlice(tokenExchanges).([]common.TokenExchange), tracerr.Wrap(err)
898}
899
900func (hdb *HistoryDB) addEscapeHatchWithdrawals(d meddler.DB,
901 escapeHatchWithdrawals []common.WDelayerEscapeHatchWithdrawal) error {
902 if len(escapeHatchWithdrawals) == 0 {
903 return nil
904 }
905 return tracerr.Wrap(db.BulkInsert(
906 d,
907 `INSERT INTO escape_hatch_withdrawal (
908 eth_block_num,
909 who_addr,
910 to_addr,
911 token_addr,
912 amount
913 ) VALUES %s;`,
914 escapeHatchWithdrawals,
915 ))
916}
917
918// GetAllEscapeHatchWithdrawals retrieves all the escape hatch withdrawals
919func (hdb *HistoryDB) GetAllEscapeHatchWithdrawals() ([]common.WDelayerEscapeHatchWithdrawal, error) {
920 var escapeHatchWithdrawals []*common.WDelayerEscapeHatchWithdrawal
921 err := meddler.QueryAll(
922 hdb.dbRead, &escapeHatchWithdrawals,
923 "SELECT eth_block_num, who_addr, to_addr, token_addr, amount FROM escape_hatch_withdrawal ORDER BY item_id;",
924 )
925 return db.SlicePtrsToSlice(escapeHatchWithdrawals).([]common.WDelayerEscapeHatchWithdrawal),
926 tracerr.Wrap(err)
927}
928
929// SetInitialSCVars sets the initial state of rollup, auction, wdelayer smart
930// contract variables. This initial state is stored linked to block 0, which
931// always exist in the DB and is used to store initialization data that always
932// exist in the smart contracts.
933func (hdb *HistoryDB) SetInitialSCVars(rollup *common.RollupVariables,
934 auction *common.AuctionVariables, wDelayer *common.WDelayerVariables) error {
935 txn, err := hdb.dbWrite.Beginx()
936 if err != nil {
937 return tracerr.Wrap(err)
938 }
939 defer func() {
940 if err != nil {
941 db.Rollback(txn)
942 }
943 }()
944 // Force EthBlockNum to be 0 because it's the block used to link data
945 // that belongs to the creation of the smart contracts
946 rollup.EthBlockNum = 0
947 auction.EthBlockNum = 0
948 wDelayer.EthBlockNum = 0
949 auction.DefaultSlotSetBidSlotNum = 0
950 if err := hdb.setRollupVars(txn, rollup); err != nil {
951 return tracerr.Wrap(err)
952 }
953 if err := hdb.setAuctionVars(txn, auction); err != nil {
954 return tracerr.Wrap(err)
955 }
956 if err := hdb.setWDelayerVars(txn, wDelayer); err != nil {
957 return tracerr.Wrap(err)
958 }
959
960 return tracerr.Wrap(txn.Commit())
961}
962
963// setExtraInfoForgedL1UserTxs sets the EffectiveAmount, EffectiveDepositAmount
964// and EffectiveFromIdx of the given l1UserTxs (with an UPDATE)
965func (hdb *HistoryDB) setExtraInfoForgedL1UserTxs(d sqlx.Ext, txs []common.L1Tx) error {
966 if len(txs) == 0 {
967 return nil
968 }
969 // Effective amounts are stored as success flags in the DB, with true value by default
970 // to reduce the amount of updates. Therefore, only amounts that became uneffective should be
971 // updated to become false. At the same time, all the txs that contain
972 // accounts (FromIdx == 0) are updated to set the EffectiveFromIdx.
973 type txUpdate struct {
974 ID common.TxID `db:"id"`
975 AmountSuccess bool `db:"amount_success"`
976 DepositAmountSuccess bool `db:"deposit_amount_success"`
977 EffectiveFromIdx common.Idx `db:"effective_from_idx"`
978 }
979 txUpdates := []txUpdate{}
980 equal := func(a *big.Int, b *big.Int) bool {
981 return a.Cmp(b) == 0
982 }
983 for i := range txs {
984 amountSuccess := equal(txs[i].Amount, txs[i].EffectiveAmount)
985 depositAmountSuccess := equal(txs[i].DepositAmount, txs[i].EffectiveDepositAmount)
986 if !amountSuccess || !depositAmountSuccess || txs[i].FromIdx == 0 {
987 txUpdates = append(txUpdates, txUpdate{
988 ID: txs[i].TxID,
989 AmountSuccess: amountSuccess,
990 DepositAmountSuccess: depositAmountSuccess,
991 EffectiveFromIdx: txs[i].EffectiveFromIdx,
992 })
993 }
994 }
995 const query string = `
996 UPDATE tx SET
997 amount_success = tx_update.amount_success,
998 deposit_amount_success = tx_update.deposit_amount_success,
999 effective_from_idx = tx_update.effective_from_idx
1000 FROM (VALUES
1001 (NULL::::BYTEA, NULL::::BOOL, NULL::::BOOL, NULL::::BIGINT),
1002 (:id, :amount_success, :deposit_amount_success, :effective_from_idx)
1003 ) as tx_update (id, amount_success, deposit_amount_success, effective_from_idx)
1004 WHERE tx.id = tx_update.id;
1005 `
1006 if len(txUpdates) > 0 {
1007 if _, err := sqlx.NamedExec(d, query, txUpdates); err != nil {
1008 return tracerr.Wrap(err)
1009 }
1010 }
1011 return nil
1012}
1013
1014// AddBlockSCData stores all the information of a block retrieved by the
1015// Synchronizer. Blocks should be inserted in order, leaving no gaps because
1016// the pagination system of the API/DB depends on this. Within blocks, all
1017// items should also be in the correct order (Accounts, Tokens, Txs, etc.)
1018func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
1019 txn, err := hdb.dbWrite.Beginx()
1020 if err != nil {
1021 return tracerr.Wrap(err)
1022 }
1023 defer func() {
1024 if err != nil {
1025 db.Rollback(txn)
1026 }
1027 }()
1028
1029 // Add block
1030 if err := hdb.addBlock(txn, &blockData.Block); err != nil {
1031 return tracerr.Wrap(err)
1032 }
1033
1034 // Add Coordinators
1035 if err := hdb.addCoordinators(txn, blockData.Auction.Coordinators); err != nil {
1036 return tracerr.Wrap(err)
1037 }
1038
1039 // Add Bids
1040 if err := hdb.addBids(txn, blockData.Auction.Bids); err != nil {
1041 return tracerr.Wrap(err)
1042 }
1043
1044 // Add Tokens
1045 if err := hdb.addTokens(txn, blockData.Rollup.AddedTokens); err != nil {
1046 return tracerr.Wrap(err)
1047 }
1048
1049 // Prepare user L1 txs to be added.
1050 // They must be added before the batch that will forge them (which can be in the same block)
1051 // and after the account that will be sent to (also can be in the same block).
1052 // Note: insert order is not relevant since item_id will be updated by a DB trigger when
1053 // the batch that forges those txs is inserted
1054 userL1s := make(map[common.BatchNum][]common.L1Tx)
1055 for i := range blockData.Rollup.L1UserTxs {
1056 batchThatForgesIsInTheBlock := false
1057 for _, batch := range blockData.Rollup.Batches {
1058 if batch.Batch.ForgeL1TxsNum != nil &&
1059 *batch.Batch.ForgeL1TxsNum == *blockData.Rollup.L1UserTxs[i].ToForgeL1TxsNum {
1060 // Tx is forged in this block. It's guaranteed that:
1061 // * the first batch of the block won't forge user L1 txs that have been added in this block
1062 // * batch nums are sequential therefore it's safe to add the tx at batch.BatchNum -1
1063 batchThatForgesIsInTheBlock = true
1064 addAtBatchNum := batch.Batch.BatchNum - 1
1065 userL1s[addAtBatchNum] = append(userL1s[addAtBatchNum], blockData.Rollup.L1UserTxs[i])
1066 break
1067 }
1068 }
1069 if !batchThatForgesIsInTheBlock {
1070 // User artificial batchNum 0 to add txs that are not forge in this block
1071 // after all the accounts of the block have been added
1072 userL1s[0] = append(userL1s[0], blockData.Rollup.L1UserTxs[i])
1073 }
1074 }
1075
1076 // Add Batches
1077 for i := range blockData.Rollup.Batches {
1078 batch := &blockData.Rollup.Batches[i]
1079
1080 // Add Batch: this will trigger an update on the DB
1081 // that will set the batch num of forged L1 txs in this batch
1082 if err = hdb.addBatch(txn, &batch.Batch); err != nil {
1083 return tracerr.Wrap(err)
1084 }
1085
1086 // Add accounts
1087 if err := hdb.addAccounts(txn, batch.CreatedAccounts); err != nil {
1088 return tracerr.Wrap(err)
1089 }
1090
1091 // Add accountBalances if it exists
1092 if err := hdb.addAccountUpdates(txn, batch.UpdatedAccounts); err != nil {
1093 return tracerr.Wrap(err)
1094 }
1095
1096 // Set the EffectiveAmount and EffectiveDepositAmount of all the
1097 // L1UserTxs that have been forged in this batch
1098 if err = hdb.setExtraInfoForgedL1UserTxs(txn, batch.L1UserTxs); err != nil {
1099 return tracerr.Wrap(err)
1100 }
1101
1102 // Add forged l1 coordinator Txs
1103 if err := hdb.addL1Txs(txn, batch.L1CoordinatorTxs); err != nil {
1104 return tracerr.Wrap(err)
1105 }
1106
1107 // Add l2 Txs
1108 if err := hdb.addL2Txs(txn, batch.L2Txs); err != nil {
1109 return tracerr.Wrap(err)
1110 }
1111
1112 // Add user L1 txs that will be forged in next batch
1113 if userlL1s, ok := userL1s[batch.Batch.BatchNum]; ok {
1114 if err := hdb.addL1Txs(txn, userlL1s); err != nil {
1115 return tracerr.Wrap(err)
1116 }
1117 }
1118
1119 // Add exit tree
1120 if err := hdb.addExitTree(txn, batch.ExitTree); err != nil {
1121 return tracerr.Wrap(err)
1122 }
1123 }
1124 // Add user L1 txs that won't be forged in this block
1125 if userL1sNotForgedInThisBlock, ok := userL1s[0]; ok {
1126 if err := hdb.addL1Txs(txn, userL1sNotForgedInThisBlock); err != nil {
1127 return tracerr.Wrap(err)
1128 }
1129 }
1130
1131 // Set SC Vars if there was an update
1132 if blockData.Rollup.Vars != nil {
1133 if err := hdb.setRollupVars(txn, blockData.Rollup.Vars); err != nil {
1134 return tracerr.Wrap(err)
1135 }
1136 }
1137 if blockData.Auction.Vars != nil {
1138 if err := hdb.setAuctionVars(txn, blockData.Auction.Vars); err != nil {
1139 return tracerr.Wrap(err)
1140 }
1141 }
1142 if blockData.WDelayer.Vars != nil {
1143 if err := hdb.setWDelayerVars(txn, blockData.WDelayer.Vars); err != nil {
1144 return tracerr.Wrap(err)
1145 }
1146 }
1147
1148 // Update withdrawals in exit tree table
1149 if err := hdb.updateExitTree(txn, blockData.Block.Num,
1150 blockData.Rollup.Withdrawals, blockData.WDelayer.Withdrawals); err != nil {
1151 return tracerr.Wrap(err)
1152 }
1153
1154 // Add Escape Hatch Withdrawals
1155 if err := hdb.addEscapeHatchWithdrawals(txn,
1156 blockData.WDelayer.EscapeHatchWithdrawals); err != nil {
1157 return tracerr.Wrap(err)
1158 }
1159
1160 // Add Buckets withdrawals updates
1161 if err := hdb.addBucketUpdates(txn, blockData.Rollup.UpdateBucketWithdraw); err != nil {
1162 return tracerr.Wrap(err)
1163 }
1164
1165 // Add Token exchange updates
1166 if err := hdb.addTokenExchanges(txn, blockData.Rollup.TokenExchanges); err != nil {
1167 return tracerr.Wrap(err)
1168 }
1169
1170 return tracerr.Wrap(txn.Commit())
1171}
1172
1173// AddAuctionVars insert auction vars into the DB
1174func (hdb *HistoryDB) AddAuctionVars(auctionVars *common.AuctionVariables) error {
1175 return tracerr.Wrap(meddler.Insert(hdb.dbWrite, "auction_vars", auctionVars))
1176}
1177
1178// GetTokensTest used to get tokens in a testing context
1179func (hdb *HistoryDB) GetTokensTest() ([]TokenWithUSD, error) {
1180 tokens := []*TokenWithUSD{}
1181 if err := meddler.QueryAll(
1182 hdb.dbRead, &tokens,
1183 "SELECT * FROM token ORDER BY token_id ASC",
1184 ); err != nil {
1185 return nil, tracerr.Wrap(err)
1186 }
1187 if len(tokens) == 0 {
1188 return []TokenWithUSD{}, nil
1189 }
1190 return db.SlicePtrsToSlice(tokens).([]TokenWithUSD), nil
1191}
1192
1193const (
1194 // CreateAccountExtraFeePercentage is the multiplication factor over
1195 // the average fee for CreateAccount that is applied to obtain the
1196 // recommended fee for CreateAccount
1197 CreateAccountExtraFeePercentage float64 = 2.5
1198 // CreateAccountInternalExtraFeePercentage is the multiplication factor
1199 // over the average fee for CreateAccountInternal that is applied to
1200 // obtain the recommended fee for CreateAccountInternal
1201 CreateAccountInternalExtraFeePercentage float64 = 2.0
1202)
1203
1204// GetRecommendedFee returns the RecommendedFee information
1205func (hdb *HistoryDB) GetRecommendedFee(minFeeUSD, maxFeeUSD float64) (*common.RecommendedFee, error) {
1206 var recommendedFee common.RecommendedFee
1207 // Get total txs and the batch of the first selected tx of the last hour
1208 type totalTxsSinceBatchNum struct {
1209 TotalTxs int `meddler:"total_txs"`
1210 FirstBatchNum common.BatchNum `meddler:"batch_num"`
1211 }
1212 ttsbn := &totalTxsSinceBatchNum{}
1213 if err := meddler.QueryRow(
1214 hdb.dbRead, ttsbn, `SELECT COUNT(tx.*) as total_txs,
1215 COALESCE (MIN(tx.batch_num), 0) as batch_num
1216 FROM tx INNER JOIN block ON tx.eth_block_num = block.eth_block_num
1217 WHERE block.timestamp >= NOW() - INTERVAL '1 HOURS';`,
1218 ); err != nil {
1219 return nil, tracerr.Wrap(err)
1220 }
1221 // Get the amount of batches and acumulated fees for the last hour
1222 type totalBatchesAndFee struct {
1223 TotalBatches int `meddler:"total_batches"`
1224 TotalFees float64 `meddler:"total_fees"`
1225 }
1226 tbf := &totalBatchesAndFee{}
1227 if err := meddler.QueryRow(
1228 hdb.dbRead, tbf, `SELECT COUNT(*) AS total_batches,
1229 COALESCE (SUM(total_fees_usd), 0) AS total_fees FROM batch
1230 WHERE batch_num > $1;`, ttsbn.FirstBatchNum,
1231 ); err != nil {
1232 return nil, tracerr.Wrap(err)
1233 }
1234 // Update NodeInfo struct
1235 var avgTransactionFee float64
1236 if ttsbn.TotalTxs > 0 {
1237 avgTransactionFee = tbf.TotalFees / float64(ttsbn.TotalTxs)
1238 } else {
1239 avgTransactionFee = 0
1240 }
1241 recommendedFee.ExistingAccount = math.Min(maxFeeUSD,
1242 math.Max(avgTransactionFee, minFeeUSD))
1243 recommendedFee.CreatesAccount = math.Min(maxFeeUSD,
1244 math.Max(CreateAccountExtraFeePercentage*avgTransactionFee, minFeeUSD))
1245 recommendedFee.CreatesAccountInternal = math.Min(maxFeeUSD,
1246 math.Max(CreateAccountInternalExtraFeePercentage*avgTransactionFee, minFeeUSD))
1247 return &recommendedFee, nil
1248}