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// GetUnforgedL1UserFutureTxs gets L1 User Txs to be forged after the L1Batch
776// with toForgeL1TxsNum (in one of the future batches, not in the next one).
777func (hdb *HistoryDB) GetUnforgedL1UserFutureTxs(toForgeL1TxsNum int64) ([]common.L1Tx, error) {
778 var txs []*common.L1Tx
779 err := meddler.QueryAll(
780 hdb.dbRead, &txs, // only L1 user txs can have batch_num set to null
781 `SELECT tx.id, tx.to_forge_l1_txs_num, tx.position, tx.user_origin,
782 tx.from_idx, tx.from_eth_addr, tx.from_bjj, tx.to_idx, tx.token_id,
783 tx.amount, NULL AS effective_amount,
784 tx.deposit_amount, NULL AS effective_deposit_amount,
785 tx.eth_block_num, tx.type, tx.batch_num
786 FROM tx WHERE batch_num IS NULL AND to_forge_l1_txs_num > $1
787 ORDER BY position;`,
788 toForgeL1TxsNum,
789 )
790 return db.SlicePtrsToSlice(txs).([]common.L1Tx), tracerr.Wrap(err)
791}
792
793// GetUnforgedL1UserTxsCount returns the count of unforged L1Txs (either in
794// open or frozen queues that are not yet forged)
795func (hdb *HistoryDB) GetUnforgedL1UserTxsCount() (int, error) {
796 row := hdb.dbRead.QueryRow(
797 `SELECT COUNT(*) FROM tx WHERE batch_num IS NULL;`,
798 )
799 var count int
800 return count, tracerr.Wrap(row.Scan(&count))
801}
802
803// TODO: Think about chaning all the queries that return a last value, to queries that return the next valid value.
804
805// GetLastTxsPosition for a given to_forge_l1_txs_num
806func (hdb *HistoryDB) GetLastTxsPosition(toForgeL1TxsNum int64) (int, error) {
807 row := hdb.dbRead.QueryRow(
808 "SELECT position FROM tx WHERE to_forge_l1_txs_num = $1 ORDER BY position DESC;",
809 toForgeL1TxsNum,
810 )
811 var lastL1TxsPosition int
812 return lastL1TxsPosition, tracerr.Wrap(row.Scan(&lastL1TxsPosition))
813}
814
815// GetSCVars returns the rollup, auction and wdelayer smart contracts variables at their last update.
816func (hdb *HistoryDB) GetSCVars() (*common.RollupVariables, *common.AuctionVariables,
817 *common.WDelayerVariables, error) {
818 var rollup common.RollupVariables
819 var auction common.AuctionVariables
820 var wDelayer common.WDelayerVariables
821 if err := meddler.QueryRow(hdb.dbRead, &rollup,
822 "SELECT * FROM rollup_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
823 return nil, nil, nil, tracerr.Wrap(err)
824 }
825 if err := meddler.QueryRow(hdb.dbRead, &auction,
826 "SELECT * FROM auction_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
827 return nil, nil, nil, tracerr.Wrap(err)
828 }
829 if err := meddler.QueryRow(hdb.dbRead, &wDelayer,
830 "SELECT * FROM wdelayer_vars ORDER BY eth_block_num DESC LIMIT 1;"); err != nil {
831 return nil, nil, nil, tracerr.Wrap(err)
832 }
833 return &rollup, &auction, &wDelayer, nil
834}
835
836func (hdb *HistoryDB) setRollupVars(d meddler.DB, rollup *common.RollupVariables) error {
837 return tracerr.Wrap(meddler.Insert(d, "rollup_vars", rollup))
838}
839
840func (hdb *HistoryDB) setAuctionVars(d meddler.DB, auction *common.AuctionVariables) error {
841 return tracerr.Wrap(meddler.Insert(d, "auction_vars", auction))
842}
843
844func (hdb *HistoryDB) setWDelayerVars(d meddler.DB, wDelayer *common.WDelayerVariables) error {
845 return tracerr.Wrap(meddler.Insert(d, "wdelayer_vars", wDelayer))
846}
847
848func (hdb *HistoryDB) addBucketUpdates(d meddler.DB, bucketUpdates []common.BucketUpdate) error {
849 if len(bucketUpdates) == 0 {
850 return nil
851 }
852 return tracerr.Wrap(db.BulkInsert(
853 d,
854 `INSERT INTO bucket_update (
855 eth_block_num,
856 num_bucket,
857 block_stamp,
858 withdrawals
859 ) VALUES %s;`,
860 bucketUpdates,
861 ))
862}
863
864// AddBucketUpdatesTest allows call to unexported method
865// only for internal testing purposes
866func (hdb *HistoryDB) AddBucketUpdatesTest(d meddler.DB, bucketUpdates []common.BucketUpdate) error {
867 return hdb.addBucketUpdates(d, bucketUpdates)
868}
869
870// GetAllBucketUpdates retrieves all the bucket updates
871func (hdb *HistoryDB) GetAllBucketUpdates() ([]common.BucketUpdate, error) {
872 var bucketUpdates []*common.BucketUpdate
873 err := meddler.QueryAll(
874 hdb.dbRead, &bucketUpdates,
875 `SELECT eth_block_num, num_bucket, block_stamp, withdrawals
876 FROM bucket_update ORDER BY item_id;`,
877 )
878 return db.SlicePtrsToSlice(bucketUpdates).([]common.BucketUpdate), tracerr.Wrap(err)
879}
880
881func (hdb *HistoryDB) getMinBidInfo(d meddler.DB,
882 currentSlot, lastClosedSlot int64) ([]MinBidInfo, error) {
883 minBidInfo := []*MinBidInfo{}
884 query := `
885 SELECT DISTINCT default_slot_set_bid, default_slot_set_bid_slot_num FROM auction_vars
886 WHERE default_slot_set_bid_slot_num < $1
887 ORDER BY default_slot_set_bid_slot_num DESC
888 LIMIT $2;`
889 err := meddler.QueryAll(d, &minBidInfo, query, lastClosedSlot, int(lastClosedSlot-currentSlot)+1)
890 return db.SlicePtrsToSlice(minBidInfo).([]MinBidInfo), tracerr.Wrap(err)
891}
892
893func (hdb *HistoryDB) addTokenExchanges(d meddler.DB, tokenExchanges []common.TokenExchange) error {
894 if len(tokenExchanges) == 0 {
895 return nil
896 }
897 return tracerr.Wrap(db.BulkInsert(
898 d,
899 `INSERT INTO token_exchange (
900 eth_block_num,
901 eth_addr,
902 value_usd
903 ) VALUES %s;`,
904 tokenExchanges,
905 ))
906}
907
908// GetAllTokenExchanges retrieves all the token exchanges
909func (hdb *HistoryDB) GetAllTokenExchanges() ([]common.TokenExchange, error) {
910 var tokenExchanges []*common.TokenExchange
911 err := meddler.QueryAll(
912 hdb.dbRead, &tokenExchanges,
913 "SELECT eth_block_num, eth_addr, value_usd FROM token_exchange ORDER BY item_id;",
914 )
915 return db.SlicePtrsToSlice(tokenExchanges).([]common.TokenExchange), tracerr.Wrap(err)
916}
917
918func (hdb *HistoryDB) addEscapeHatchWithdrawals(d meddler.DB,
919 escapeHatchWithdrawals []common.WDelayerEscapeHatchWithdrawal) error {
920 if len(escapeHatchWithdrawals) == 0 {
921 return nil
922 }
923 return tracerr.Wrap(db.BulkInsert(
924 d,
925 `INSERT INTO escape_hatch_withdrawal (
926 eth_block_num,
927 who_addr,
928 to_addr,
929 token_addr,
930 amount
931 ) VALUES %s;`,
932 escapeHatchWithdrawals,
933 ))
934}
935
936// GetAllEscapeHatchWithdrawals retrieves all the escape hatch withdrawals
937func (hdb *HistoryDB) GetAllEscapeHatchWithdrawals() ([]common.WDelayerEscapeHatchWithdrawal, error) {
938 var escapeHatchWithdrawals []*common.WDelayerEscapeHatchWithdrawal
939 err := meddler.QueryAll(
940 hdb.dbRead, &escapeHatchWithdrawals,
941 "SELECT eth_block_num, who_addr, to_addr, token_addr, amount FROM escape_hatch_withdrawal ORDER BY item_id;",
942 )
943 return db.SlicePtrsToSlice(escapeHatchWithdrawals).([]common.WDelayerEscapeHatchWithdrawal),
944 tracerr.Wrap(err)
945}
946
947// SetInitialSCVars sets the initial state of rollup, auction, wdelayer smart
948// contract variables. This initial state is stored linked to block 0, which
949// always exist in the DB and is used to store initialization data that always
950// exist in the smart contracts.
951func (hdb *HistoryDB) SetInitialSCVars(rollup *common.RollupVariables,
952 auction *common.AuctionVariables, wDelayer *common.WDelayerVariables) error {
953 txn, err := hdb.dbWrite.Beginx()
954 if err != nil {
955 return tracerr.Wrap(err)
956 }
957 defer func() {
958 if err != nil {
959 db.Rollback(txn)
960 }
961 }()
962 // Force EthBlockNum to be 0 because it's the block used to link data
963 // that belongs to the creation of the smart contracts
964 rollup.EthBlockNum = 0
965 auction.EthBlockNum = 0
966 wDelayer.EthBlockNum = 0
967 auction.DefaultSlotSetBidSlotNum = 0
968 if err := hdb.setRollupVars(txn, rollup); err != nil {
969 return tracerr.Wrap(err)
970 }
971 if err := hdb.setAuctionVars(txn, auction); err != nil {
972 return tracerr.Wrap(err)
973 }
974 if err := hdb.setWDelayerVars(txn, wDelayer); err != nil {
975 return tracerr.Wrap(err)
976 }
977
978 return tracerr.Wrap(txn.Commit())
979}
980
981// setExtraInfoForgedL1UserTxs sets the EffectiveAmount, EffectiveDepositAmount
982// and EffectiveFromIdx of the given l1UserTxs (with an UPDATE)
983func (hdb *HistoryDB) setExtraInfoForgedL1UserTxs(d sqlx.Ext, txs []common.L1Tx) error {
984 if len(txs) == 0 {
985 return nil
986 }
987 // Effective amounts are stored as success flags in the DB, with true value by default
988 // to reduce the amount of updates. Therefore, only amounts that became uneffective should be
989 // updated to become false. At the same time, all the txs that contain
990 // accounts (FromIdx == 0) are updated to set the EffectiveFromIdx.
991 type txUpdate struct {
992 ID common.TxID `db:"id"`
993 AmountSuccess bool `db:"amount_success"`
994 DepositAmountSuccess bool `db:"deposit_amount_success"`
995 EffectiveFromIdx common.Idx `db:"effective_from_idx"`
996 }
997 txUpdates := []txUpdate{}
998 equal := func(a *big.Int, b *big.Int) bool {
999 return a.Cmp(b) == 0
1000 }
1001 for i := range txs {
1002 amountSuccess := equal(txs[i].Amount, txs[i].EffectiveAmount)
1003 depositAmountSuccess := equal(txs[i].DepositAmount, txs[i].EffectiveDepositAmount)
1004 if !amountSuccess || !depositAmountSuccess || txs[i].FromIdx == 0 {
1005 txUpdates = append(txUpdates, txUpdate{
1006 ID: txs[i].TxID,
1007 AmountSuccess: amountSuccess,
1008 DepositAmountSuccess: depositAmountSuccess,
1009 EffectiveFromIdx: txs[i].EffectiveFromIdx,
1010 })
1011 }
1012 }
1013 const query string = `
1014 UPDATE tx SET
1015 amount_success = tx_update.amount_success,
1016 deposit_amount_success = tx_update.deposit_amount_success,
1017 effective_from_idx = tx_update.effective_from_idx
1018 FROM (VALUES
1019 (NULL::::BYTEA, NULL::::BOOL, NULL::::BOOL, NULL::::BIGINT),
1020 (:id, :amount_success, :deposit_amount_success, :effective_from_idx)
1021 ) as tx_update (id, amount_success, deposit_amount_success, effective_from_idx)
1022 WHERE tx.id = tx_update.id;
1023 `
1024 if len(txUpdates) > 0 {
1025 if _, err := sqlx.NamedExec(d, query, txUpdates); err != nil {
1026 return tracerr.Wrap(err)
1027 }
1028 }
1029 return nil
1030}
1031
1032// AddBlockSCData stores all the information of a block retrieved by the
1033// Synchronizer. Blocks should be inserted in order, leaving no gaps because
1034// the pagination system of the API/DB depends on this. Within blocks, all
1035// items should also be in the correct order (Accounts, Tokens, Txs, etc.)
1036func (hdb *HistoryDB) AddBlockSCData(blockData *common.BlockData) (err error) {
1037 txn, err := hdb.dbWrite.Beginx()
1038 if err != nil {
1039 return tracerr.Wrap(err)
1040 }
1041 defer func() {
1042 if err != nil {
1043 db.Rollback(txn)
1044 }
1045 }()
1046
1047 // Add block
1048 if err := hdb.addBlock(txn, &blockData.Block); err != nil {
1049 return tracerr.Wrap(err)
1050 }
1051
1052 // Add Coordinators
1053 if err := hdb.addCoordinators(txn, blockData.Auction.Coordinators); err != nil {
1054 return tracerr.Wrap(err)
1055 }
1056
1057 // Add Bids
1058 if err := hdb.addBids(txn, blockData.Auction.Bids); err != nil {
1059 return tracerr.Wrap(err)
1060 }
1061
1062 // Add Tokens
1063 if err := hdb.addTokens(txn, blockData.Rollup.AddedTokens); err != nil {
1064 return tracerr.Wrap(err)
1065 }
1066
1067 // Prepare user L1 txs to be added.
1068 // They must be added before the batch that will forge them (which can be in the same block)
1069 // and after the account that will be sent to (also can be in the same block).
1070 // Note: insert order is not relevant since item_id will be updated by a DB trigger when
1071 // the batch that forges those txs is inserted
1072 userL1s := make(map[common.BatchNum][]common.L1Tx)
1073 for i := range blockData.Rollup.L1UserTxs {
1074 batchThatForgesIsInTheBlock := false
1075 for _, batch := range blockData.Rollup.Batches {
1076 if batch.Batch.ForgeL1TxsNum != nil &&
1077 *batch.Batch.ForgeL1TxsNum == *blockData.Rollup.L1UserTxs[i].ToForgeL1TxsNum {
1078 // Tx is forged in this block. It's guaranteed that:
1079 // * the first batch of the block won't forge user L1 txs that have been added in this block
1080 // * batch nums are sequential therefore it's safe to add the tx at batch.BatchNum -1
1081 batchThatForgesIsInTheBlock = true
1082 addAtBatchNum := batch.Batch.BatchNum - 1
1083 userL1s[addAtBatchNum] = append(userL1s[addAtBatchNum], blockData.Rollup.L1UserTxs[i])
1084 break
1085 }
1086 }
1087 if !batchThatForgesIsInTheBlock {
1088 // User artificial batchNum 0 to add txs that are not forge in this block
1089 // after all the accounts of the block have been added
1090 userL1s[0] = append(userL1s[0], blockData.Rollup.L1UserTxs[i])
1091 }
1092 }
1093
1094 // Add Batches
1095 for i := range blockData.Rollup.Batches {
1096 batch := &blockData.Rollup.Batches[i]
1097
1098 // Add Batch: this will trigger an update on the DB
1099 // that will set the batch num of forged L1 txs in this batch
1100 if err = hdb.addBatch(txn, &batch.Batch); err != nil {
1101 return tracerr.Wrap(err)
1102 }
1103
1104 // Add accounts
1105 if err := hdb.addAccounts(txn, batch.CreatedAccounts); err != nil {
1106 return tracerr.Wrap(err)
1107 }
1108
1109 // Add accountBalances if it exists
1110 if err := hdb.addAccountUpdates(txn, batch.UpdatedAccounts); err != nil {
1111 return tracerr.Wrap(err)
1112 }
1113
1114 // Set the EffectiveAmount and EffectiveDepositAmount of all the
1115 // L1UserTxs that have been forged in this batch
1116 if err = hdb.setExtraInfoForgedL1UserTxs(txn, batch.L1UserTxs); err != nil {
1117 return tracerr.Wrap(err)
1118 }
1119
1120 // Add forged l1 coordinator Txs
1121 if err := hdb.addL1Txs(txn, batch.L1CoordinatorTxs); err != nil {
1122 return tracerr.Wrap(err)
1123 }
1124
1125 // Add l2 Txs
1126 if err := hdb.addL2Txs(txn, batch.L2Txs); err != nil {
1127 return tracerr.Wrap(err)
1128 }
1129
1130 // Add user L1 txs that will be forged in next batch
1131 if userlL1s, ok := userL1s[batch.Batch.BatchNum]; ok {
1132 if err := hdb.addL1Txs(txn, userlL1s); err != nil {
1133 return tracerr.Wrap(err)
1134 }
1135 }
1136
1137 // Add exit tree
1138 if err := hdb.addExitTree(txn, batch.ExitTree); err != nil {
1139 return tracerr.Wrap(err)
1140 }
1141 }
1142 // Add user L1 txs that won't be forged in this block
1143 if userL1sNotForgedInThisBlock, ok := userL1s[0]; ok {
1144 if err := hdb.addL1Txs(txn, userL1sNotForgedInThisBlock); err != nil {
1145 return tracerr.Wrap(err)
1146 }
1147 }
1148
1149 // Set SC Vars if there was an update
1150 if blockData.Rollup.Vars != nil {
1151 if err := hdb.setRollupVars(txn, blockData.Rollup.Vars); err != nil {
1152 return tracerr.Wrap(err)
1153 }
1154 }
1155 if blockData.Auction.Vars != nil {
1156 if err := hdb.setAuctionVars(txn, blockData.Auction.Vars); err != nil {
1157 return tracerr.Wrap(err)
1158 }
1159 }
1160 if blockData.WDelayer.Vars != nil {
1161 if err := hdb.setWDelayerVars(txn, blockData.WDelayer.Vars); err != nil {
1162 return tracerr.Wrap(err)
1163 }
1164 }
1165
1166 // Update withdrawals in exit tree table
1167 if err := hdb.updateExitTree(txn, blockData.Block.Num,
1168 blockData.Rollup.Withdrawals, blockData.WDelayer.Withdrawals); err != nil {
1169 return tracerr.Wrap(err)
1170 }
1171
1172 // Add Escape Hatch Withdrawals
1173 if err := hdb.addEscapeHatchWithdrawals(txn,
1174 blockData.WDelayer.EscapeHatchWithdrawals); err != nil {
1175 return tracerr.Wrap(err)
1176 }
1177
1178 // Add Buckets withdrawals updates
1179 if err := hdb.addBucketUpdates(txn, blockData.Rollup.UpdateBucketWithdraw); err != nil {
1180 return tracerr.Wrap(err)
1181 }
1182
1183 // Add Token exchange updates
1184 if err := hdb.addTokenExchanges(txn, blockData.Rollup.TokenExchanges); err != nil {
1185 return tracerr.Wrap(err)
1186 }
1187
1188 return tracerr.Wrap(txn.Commit())
1189}
1190
1191// AddAuctionVars insert auction vars into the DB
1192func (hdb *HistoryDB) AddAuctionVars(auctionVars *common.AuctionVariables) error {
1193 return tracerr.Wrap(meddler.Insert(hdb.dbWrite, "auction_vars", auctionVars))
1194}
1195
1196// GetTokensTest used to get tokens in a testing context
1197func (hdb *HistoryDB) GetTokensTest() ([]TokenWithUSD, error) {
1198 tokens := []*TokenWithUSD{}
1199 if err := meddler.QueryAll(
1200 hdb.dbRead, &tokens,
1201 "SELECT * FROM token ORDER BY token_id ASC",
1202 ); err != nil {
1203 return nil, tracerr.Wrap(err)
1204 }
1205 if len(tokens) == 0 {
1206 return []TokenWithUSD{}, nil
1207 }
1208 return db.SlicePtrsToSlice(tokens).([]TokenWithUSD), nil
1209}
1210
1211const (
1212 // CreateAccountExtraFeePercentage is the multiplication factor over
1213 // the average fee for CreateAccount that is applied to obtain the
1214 // recommended fee for CreateAccount
1215 CreateAccountExtraFeePercentage float64 = 2.5
1216 // CreateAccountInternalExtraFeePercentage is the multiplication factor
1217 // over the average fee for CreateAccountInternal that is applied to
1218 // obtain the recommended fee for CreateAccountInternal
1219 CreateAccountInternalExtraFeePercentage float64 = 2.0
1220)
1221
1222// GetRecommendedFee returns the RecommendedFee information
1223func (hdb *HistoryDB) GetRecommendedFee(minFeeUSD, maxFeeUSD float64) (*common.RecommendedFee, error) {
1224 var recommendedFee common.RecommendedFee
1225 // Get total txs and the batch of the first selected tx of the last hour
1226 type totalTxsSinceBatchNum struct {
1227 TotalTxs int `meddler:"total_txs"`
1228 FirstBatchNum common.BatchNum `meddler:"batch_num"`
1229 }
1230 ttsbn := &totalTxsSinceBatchNum{}
1231 if err := meddler.QueryRow(
1232 hdb.dbRead, ttsbn, `SELECT COUNT(tx.*) as total_txs,
1233 COALESCE (MIN(tx.batch_num), 0) as batch_num
1234 FROM tx INNER JOIN block ON tx.eth_block_num = block.eth_block_num
1235 WHERE block.timestamp >= NOW() - INTERVAL '1 HOURS';`,
1236 ); err != nil {
1237 return nil, tracerr.Wrap(err)
1238 }
1239 // Get the amount of batches and acumulated fees for the last hour
1240 type totalBatchesAndFee struct {
1241 TotalBatches int `meddler:"total_batches"`
1242 TotalFees float64 `meddler:"total_fees"`
1243 }
1244 tbf := &totalBatchesAndFee{}
1245 if err := meddler.QueryRow(
1246 hdb.dbRead, tbf, `SELECT COUNT(*) AS total_batches,
1247 COALESCE (SUM(total_fees_usd), 0) AS total_fees FROM batch
1248 WHERE batch_num > $1;`, ttsbn.FirstBatchNum,
1249 ); err != nil {
1250 return nil, tracerr.Wrap(err)
1251 }
1252 // Update NodeInfo struct
1253 var avgTransactionFee float64
1254 if ttsbn.TotalTxs > 0 {
1255 avgTransactionFee = tbf.TotalFees / float64(ttsbn.TotalTxs)
1256 } else {
1257 avgTransactionFee = 0
1258 }
1259 recommendedFee.ExistingAccount = math.Min(maxFeeUSD,
1260 math.Max(avgTransactionFee, minFeeUSD))
1261 recommendedFee.CreatesAccount = math.Min(maxFeeUSD,
1262 math.Max(CreateAccountExtraFeePercentage*avgTransactionFee, minFeeUSD))
1263 recommendedFee.CreatesAccountInternal = math.Min(maxFeeUSD,
1264 math.Max(CreateAccountInternalExtraFeePercentage*avgTransactionFee, minFeeUSD))
1265 return &recommendedFee, nil
1266}