Compare commits

..

11 Commits

Author SHA1 Message Date
Mikelle
0511acd5e6 fixed txshistory_test.go 2021-03-31 12:48:49 +03:00
Mikelle
8d087e2727 added fromIdx and toIdx to transactionHistory 2021-03-31 12:33:57 +03:00
Mikelle
22ffd93292 implemented fromIdx and toIdx in transaction-pool request 2021-03-30 12:10:13 +03:00
Mikelle
b565c9da1a fixed lint errors 2021-03-29 14:01:01 +03:00
Mikelle
7901705dfd Merge branch 'develop' into feature/getPoolTxs 2021-03-29 13:42:01 +03:00
Mikelle
3a706e7775 implemented get pool txs endpoint 2021-03-29 13:41:36 +03:00
Mikelle
c84b3a4d0f implemented get pool txs endpoint 2021-03-29 13:39:05 +03:00
Eduard S
3f643f022a Merge pull request #677 from hermeznetwork/feature/fastsync-get-headerByNumber
Faster synchronization by fetching only block headers
2021-03-29 11:10:24 +02:00
Danilo Pantani
b8d339d568 Merge pull request #670 from hermeznetwork/fix/remove-release-os
fix the invalid goarch build
2021-03-26 16:16:29 -03:00
Pantani
a1eea43443 fix the invalid goarch build and avoid calling the migration-pack each build 2021-03-24 10:41:32 -03:00
Oleksandr Brezhniev
2125812e90 Faster synchronization with usage of HeaderByNumber instead of BlockByNumber 2021-03-23 23:09:31 +02:00
19 changed files with 320 additions and 115 deletions

3
.gitignore vendored
View File

@@ -1 +1,2 @@
bin/ bin/
dist/

View File

@@ -1,6 +1,7 @@
before: before:
hooks: hooks:
- go mod download - go mod download
- make migration-pack
builds: builds:
- main: ./cli/node/main.go - main: ./cli/node/main.go
@@ -9,10 +10,8 @@ builds:
goos: goos:
- linux - linux
- darwin - darwin
- windows goarch:
hooks: - amd64
pre: make migration-pack
post: make migration-clean
archives: archives:
- replacements: - replacements:

View File

@@ -1,27 +1,3 @@
/*
Package api implements the public interface of the hermez-node using a HTTP REST API.
There are two subsets of endpoints:
- coordinatorEndpoints: used to receive L2 transactions and account creation authorizations. Targeted for wallets.
- explorerEndpoints: used to provide all sorts of information about the network. Targeted for explorers and similar services.
About the configuration of the API:
- The API is supposed to be launched using the cli found at the package cli/node, and configured through the configuration file.
- The mentioned configuration file allows exposing any combination of the endpoint subsets.
- Although the API can run in a "standalone" manner using the serveapi command, it won't work properly
unless another process acting as a coord or sync is filling the HistoryDB.
Design principles and considerations:
- In order to decouple the API process from the rest of the node, all the communication between this package and the rest of
the system is done through the SQL database. As a matter of fact, the only public function of the package is the constructor NewAPI.
All the information needed for the API to work should be obtained through the configuration file of the cli or the database.
- The format of the requests / responses doesn't match directly with the common types, and for this reason, the package api/apitypes is used
to facilitate the format conversion. Most of the time, this is done directly at the db level.
- The API endpoints are fully documented using OpenAPI aka Swagger. All the endpoints are tested against the spec to ensure consistency
between implementation and specification. To get a sense of which endpoints exist and how they work, it's strongly recommended to check this specification.
The specification can be found at api/swagger.yml.
- In general, all the API endpoints produce queries to the SQL database in order to retrieve / insert the requested information. The most notable exceptions to this are
the /config endpoint, which returns a static object generated at construction time, and the /state, which also is retrieved from the database, but it's generated by API/stateapiupdater package.
*/
package api package api
import ( import (
@@ -51,6 +27,7 @@ func NewAPI(
l2db *l2db.L2DB, l2db *l2db.L2DB,
) (*API, error) { ) (*API, error) {
// Check input // Check input
// TODO: is stateDB only needed for explorer endpoints or for both?
if coordinatorEndpoints && l2db == nil { if coordinatorEndpoints && l2db == nil {
return nil, tracerr.Wrap(errors.New("cannot serve Coordinator endpoints without L2DB")) return nil, tracerr.Wrap(errors.New("cannot serve Coordinator endpoints without L2DB"))
} }
@@ -77,12 +54,13 @@ func NewAPI(
// Add coordinator endpoints // Add coordinator endpoints
if coordinatorEndpoints { if coordinatorEndpoints {
// Account creation authorization // Account
v1.POST("/account-creation-authorization", a.postAccountCreationAuth) v1.POST("/account-creation-authorization", a.postAccountCreationAuth)
v1.GET("/account-creation-authorization/:hezEthereumAddress", a.getAccountCreationAuth) v1.GET("/account-creation-authorization/:hezEthereumAddress", a.getAccountCreationAuth)
// Transaction // Transaction
v1.POST("/transactions-pool", a.postPoolTx) v1.POST("/transactions-pool", a.postPoolTx)
v1.GET("/transactions-pool/:id", a.getPoolTx) v1.GET("/transactions-pool/:id", a.getPoolTx)
v1.GET("/transactions-pool", a.getPoolTxs)
} }
// Add explorer endpoints // Add explorer endpoints
@@ -95,23 +73,17 @@ func NewAPI(
// Transaction // Transaction
v1.GET("/transactions-history", a.getHistoryTxs) v1.GET("/transactions-history", a.getHistoryTxs)
v1.GET("/transactions-history/:id", a.getHistoryTx) v1.GET("/transactions-history/:id", a.getHistoryTx)
// Batches // Status
v1.GET("/batches", a.getBatches) v1.GET("/batches", a.getBatches)
v1.GET("/batches/:batchNum", a.getBatch) v1.GET("/batches/:batchNum", a.getBatch)
v1.GET("/full-batches/:batchNum", a.getFullBatch) v1.GET("/full-batches/:batchNum", a.getFullBatch)
// Slots
v1.GET("/slots", a.getSlots) v1.GET("/slots", a.getSlots)
v1.GET("/slots/:slotNum", a.getSlot) v1.GET("/slots/:slotNum", a.getSlot)
// Bids
v1.GET("/bids", a.getBids) v1.GET("/bids", a.getBids)
// State
v1.GET("/state", a.getState) v1.GET("/state", a.getState)
// Config
v1.GET("/config", a.getConfig) v1.GET("/config", a.getConfig)
// Tokens
v1.GET("/tokens", a.getTokens) v1.GET("/tokens", a.getTokens)
v1.GET("/tokens/:id", a.getToken) v1.GET("/tokens/:id", a.getToken)
// Coordinators
v1.GET("/coordinators", a.getCoordinators) v1.GET("/coordinators", a.getCoordinators)
} }

View File

@@ -1,11 +1,3 @@
/*
Package apitypes is used to map the common types used across the node with the format expected by the API.
This is done using different strategies:
- Marshallers: they get triggered when the API marshals the response structs into JSONs
- Scanners/Valuers: they get triggered when a struct is sent/received to/from the SQL database
- Adhoc functions: when the already mentioned strategies are not suitable, functions are added to the structs to facilitate the conversions
*/
package apitypes package apitypes
import ( import (

View File

@@ -109,7 +109,7 @@ func (a *API) getFullBatch(c *gin.Context) {
// Fetch txs forged in the batch from historyDB // Fetch txs forged in the batch from historyDB
maxTxsPerBatch := uint(2048) //nolint:gomnd maxTxsPerBatch := uint(2048) //nolint:gomnd
txs, _, err := a.h.GetTxsAPI( txs, _, err := a.h.GetTxsAPI(
nil, nil, nil, nil, batchNum, nil, nil, &maxTxsPerBatch, historydb.OrderAsc, nil, nil, nil, nil, nil, batchNum, nil, nil, &maxTxsPerBatch, historydb.OrderAsc,
) )
if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows { if err != nil && tracerr.Unwrap(err) != sql.ErrNoRows {
retSQLErr(err, c) retSQLErr(err, c)

View File

@@ -96,6 +96,32 @@ func parseQueryBJJ(c querier) (*babyjub.PublicKeyComp, error) {
return hezStringToBJJ(bjjStr, name) return hezStringToBJJ(bjjStr, name)
} }
func parseQueryPoolL2TxState(c querier) (*common.PoolL2TxState, error) {
const name = "state"
stateStr := c.Query(name)
if stateStr == "" {
return nil, nil
}
switch common.PoolL2TxState(stateStr) {
case common.PoolL2TxStatePending:
ret := common.PoolL2TxStatePending
return &ret, nil
case common.PoolL2TxStateForged:
ret := common.PoolL2TxStateForged
return &ret, nil
case common.PoolL2TxStateForging:
ret := common.PoolL2TxStateForging
return &ret, nil
case common.PoolL2TxStateInvalid:
ret := common.PoolL2TxStateInvalid
return &ret, nil
}
return nil, tracerr.Wrap(fmt.Errorf(
"invalid %s, %s is not a valid option. Check the valid options in the docmentation",
name, stateStr,
))
}
func parseQueryTxType(c querier) (*common.TxType, error) { func parseQueryTxType(c querier) (*common.TxType, error) {
const name = "type" const name = "type"
typeStr := c.Query(name) typeStr := c.Query(name)
@@ -146,6 +172,18 @@ func parseIdx(c querier) (*common.Idx, error) {
return stringToIdx(idxStr, name) return stringToIdx(idxStr, name)
} }
func parseFromIdx(c querier) (*common.Idx, error) {
const name = "fromAccountIndex"
idxStr := c.Query(name)
return stringToIdx(idxStr, name)
}
func parseToIdx(c querier) (*common.Idx, error) {
const name = "toAccountIndex"
idxStr := c.Query(name)
return stringToIdx(idxStr, name)
}
func parseExitFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKeyComp, *common.Idx, error) { func parseExitFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.PublicKeyComp, *common.Idx, error) {
// TokenID // TokenID
tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c) tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
@@ -181,6 +219,47 @@ func parseExitFilters(c querier) (*common.TokenID, *ethCommon.Address, *babyjub.
return tokenID, addr, bjj, idx, nil return tokenID, addr, bjj, idx, nil
} }
func parseTxsHistoryFilters(c querier) (*common.TokenID, *ethCommon.Address,
*babyjub.PublicKeyComp, *common.Idx, *common.Idx, error) {
// TokenID
tid, err := parseQueryUint("tokenId", nil, 0, maxUint32, c)
if err != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(err)
}
var tokenID *common.TokenID
if tid != nil {
tokenID = new(common.TokenID)
*tokenID = common.TokenID(*tid)
}
// Hez Eth addr
addr, err := parseQueryHezEthAddr(c)
if err != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(err)
}
// BJJ
bjj, err := parseQueryBJJ(c)
if err != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(err)
}
if addr != nil && bjj != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(errors.New("bjj and hezEthereumAddress params are incompatible"))
}
// from Idx
fromIdx, err := parseFromIdx(c)
if err != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(err)
}
// to Idx
toIdx, err := parseToIdx(c)
if err != nil {
return nil, nil, nil, nil, nil, tracerr.Wrap(err)
}
if (fromIdx != nil || toIdx != nil) && (addr != nil || bjj != nil || tokenID != nil) {
return nil, nil, nil, nil, nil, tracerr.Wrap(errors.New("accountIndex is incompatible with BJJ, hezEthereumAddress and tokenId"))
}
return tokenID, addr, bjj, fromIdx, toIdx, nil
}
func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) { func parseTokenFilters(c querier) ([]common.TokenID, []string, string, error) {
idsStr := c.Query("ids") idsStr := c.Query("ids")
symbolsStr := c.Query("symbols") symbolsStr := c.Query("symbols")

View File

@@ -1,10 +1,3 @@
/*
Package stateapiupdater is responsible for generating and storing the object response of the GET /state endpoint exposed through the api package.
This object is extensively defined at the OpenAPI spec located at api/swagger.yml.
Deployment considerations: in a setup where multiple processes are used (dedicated api process, separated coord / sync, ...), only one process should care
of using this package.
*/
package stateapiupdater package stateapiupdater
import ( import (

View File

@@ -415,6 +415,55 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Error500' $ref: '#/components/schemas/Error500'
get:
tags:
- Coordinator
summary: Get transactions that are in the pool.
operationId: getPoolTxs
parameters:
- name: state
in: query
required: false
description: State of the transactions, e.g. "pend"
schema:
$ref: '#/components/schemas/PoolL2TransactionState'
- name: fromAccountIndex
in: query
required: false
description: Id of the from account
schema:
$ref: '#/components/schemas/AccountIndex'
- name: toAccountIndex
in: query
required: false
description: Id of the to account
schema:
$ref: '#/components/schemas/AccountIndex'
responses:
'200':
description: Successful operation.
content:
application/json:
schema:
$ref: '#/components/schemas/PoolL2Transactions'
'400':
description: Bad request.
content:
application/json:
schema:
$ref: '#/components/schemas/Error400'
'404':
description: Not found.
content:
application/json:
schema:
$ref: '#/components/schemas/Error404'
'500':
description: Internal server error
content:
application/json:
schema:
$ref: '#/components/schemas/Error500'
'/transactions-pool/{id}': '/transactions-pool/{id}':
get: get:
tags: tags:
@@ -487,10 +536,16 @@ paths:
required: false required: false
schema: schema:
$ref: '#/components/schemas/BJJ' $ref: '#/components/schemas/BJJ'
- name: accountIndex - name: fromAccountIndex
in: query in: query
required: false required: false
description: Only get transactions sent from or to a specific account. Incompatible with the queries `tokenId`, `hezEthereumAddress` and `BJJ`. description: Only get transactions sent from a specific account. Incompatible with the queries `tokenId`, `hezEthereumAddress` and `BJJ`.
schema:
$ref: '#/components/schemas/AccountIndex'
- name: toAccountIndex
in: query
required: false
description: Only get transactions sent to a specific account. Incompatible with the queries `tokenId`, `hezEthereumAddress` and `BJJ`.
schema: schema:
$ref: '#/components/schemas/AccountIndex' $ref: '#/components/schemas/AccountIndex'
- name: batchNum - name: batchNum
@@ -1439,6 +1494,14 @@ components:
- requestFee - requestFee
- requestNonce - requestNonce
- token - token
PoolL2Transactions:
type: object
properties:
transactions:
type: array
description: List of pool l2 transactions
items:
$ref: '#/components/schemas/PoolL2Transaction'
TransactionId: TransactionId:
type: string type: string
description: Identifier for transactions. Used for any kind of transaction (both L1 and L2). More info on how the identifiers are built [here](https://idocs.hermez.io/#/spec/architecture/db/README?id=txid) description: Identifier for transactions. Used for any kind of transaction (both L1 and L2). More info on how the identifiers are built [here](https://idocs.hermez.io/#/spec/architecture/db/README?id=txid)

View File

@@ -9,7 +9,7 @@ import (
func (a *API) getHistoryTxs(c *gin.Context) { func (a *API) getHistoryTxs(c *gin.Context) {
// Get query parameters // Get query parameters
tokenID, addr, bjj, idx, err := parseExitFilters(c) tokenID, addr, bjj, fromIdx, toIdx, err := parseTxsHistoryFilters(c)
if err != nil { if err != nil {
retBadReq(err, c) retBadReq(err, c)
return return
@@ -35,7 +35,7 @@ func (a *API) getHistoryTxs(c *gin.Context) {
// Fetch txs from historyDB // Fetch txs from historyDB
txs, pendingItems, err := a.h.GetTxsAPI( txs, pendingItems, err := a.h.GetTxsAPI(
addr, bjj, tokenID, idx, batchNum, txType, fromItem, limit, order, addr, bjj, tokenID, fromIdx, toIdx, batchNum, txType, fromItem, limit, order,
) )
if err != nil { if err != nil {
retSQLErr(err, c) retSQLErr(err, c)

View File

@@ -324,8 +324,8 @@ func TestGetHistoryTxs(t *testing.T) {
idx, err := stringToIdx(idxStr, "") idx, err := stringToIdx(idxStr, "")
assert.NoError(t, err) assert.NoError(t, err)
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?accountIndex=%s&limit=%d", "%s?fromAccountIndex=%s&toAccountIndex=%s&limit=%d",
endpoint, idxStr, limit, endpoint, idxStr, idxStr, limit,
) )
err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter) err = doGoodReqPaginated(path, historydb.OrderAsc, &testTxsResponse{}, appendIter)
assert.NoError(t, err) assert.NoError(t, err)
@@ -431,8 +431,8 @@ func TestGetHistoryTxs(t *testing.T) {
assertTxs(t, []testTx{}, fetchedTxs) assertTxs(t, []testTx{}, fetchedTxs)
// 400 // 400
path = fmt.Sprintf( path = fmt.Sprintf(
"%s?accountIndex=%s&hezEthereumAddress=%s", "%s?fromAccountIndex=%s&toAccountIndex=%s&hezEthereumAddress=%s",
endpoint, idx, account.EthAddr, endpoint, idx, idx, account.EthAddr,
) )
err = doBadReq("GET", path, nil, 400) err = doBadReq("GET", path, nil, 400)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -55,6 +55,41 @@ func (a *API) getPoolTx(c *gin.Context) {
c.JSON(http.StatusOK, tx) c.JSON(http.StatusOK, tx)
} }
func (a *API) getPoolTxs(c *gin.Context) {
// Get from idx
fromIdx, err := parseFromIdx(c)
if err != nil {
retBadReq(err, c)
return
}
// Get to idx
toIdx, err := parseToIdx(c)
if err != nil {
retBadReq(err, c)
return
}
// Get state
state, err := parseQueryPoolL2TxState(c)
if err != nil {
retBadReq(err, c)
return
}
// Fetch txs from l2DB
txs, err := a.l2.GetPoolTxs(fromIdx, toIdx, state)
if err != nil {
retSQLErr(err, c)
return
}
// Build successful response
type txsResponse struct {
Txs []*l2db.PoolTxAPI `json:"transactions"`
}
c.JSON(http.StatusOK, &txsResponse{
Txs: txs,
})
}
type receivedPoolTx struct { type receivedPoolTx struct {
TxID common.TxID `json:"id" binding:"required"` TxID common.TxID `json:"id" binding:"required"`
Type common.TxType `json:"type" binding:"required"` Type common.TxType `json:"type" binding:"required"`

View File

@@ -47,6 +47,10 @@ type testPoolTxReceive struct {
Token historydb.TokenWithUSD `json:"token"` Token historydb.TokenWithUSD `json:"token"`
} }
type testPoolTxsResponse struct {
Txs []testPoolTxReceive `json:"transactions"`
}
// testPoolTxSend is a struct to be used as a JSON body // testPoolTxSend is a struct to be used as a JSON body
// when testing POST /transactions-pool // when testing POST /transactions-pool
type testPoolTxSend struct { type testPoolTxSend struct {
@@ -225,6 +229,24 @@ func TestPoolTxs(t *testing.T) {
err = doBadReq("POST", endpoint, jsonTxReader, 400) err = doBadReq("POST", endpoint, jsonTxReader, 400)
require.NoError(t, err) require.NoError(t, err)
// GET // GET
// get by idx
fetchedTxs := testPoolTxsResponse{}
require.NoError(t, doGoodReq(
"GET",
endpoint+"?fromAccountIndex=hez:ETH:263",
nil, &fetchedTxs))
assert.Equal(t, 1, len(fetchedTxs.Txs))
assert.Equal(t, "hez:ETH:263", fetchedTxs.Txs[0].FromIdx)
// get by state
require.NoError(t, doGoodReq(
"GET",
endpoint+"?state=pend",
nil, &fetchedTxs))
assert.Equal(t, 4, len(fetchedTxs.Txs))
for _, v := range fetchedTxs.Txs {
assert.Equal(t, common.PoolL2TxStatePending, v.State)
}
// GET
endpoint += "/" endpoint += "/"
for _, tx := range tc.poolTxsToReceive { for _, tx := range tc.poolTxsToReceive {
fetchedTx := testPoolTxReceive{} fetchedTx := testPoolTxReceive{}

View File

@@ -456,7 +456,7 @@ func (hdb *HistoryDB) GetTxAPI(txID common.TxID) (*TxAPI, error) {
// and pagination info // and pagination info
func (hdb *HistoryDB) GetTxsAPI( func (hdb *HistoryDB) GetTxsAPI(
ethAddr *ethCommon.Address, bjj *babyjub.PublicKeyComp, ethAddr *ethCommon.Address, bjj *babyjub.PublicKeyComp,
tokenID *common.TokenID, idx *common.Idx, batchNum *uint, txType *common.TxType, tokenID *common.TokenID, fromIdx, toIdx *common.Idx, batchNum *uint, txType *common.TxType,
fromItem, limit *uint, order string, fromItem, limit *uint, order string,
) ([]TxAPI, uint64, error) { ) ([]TxAPI, uint64, error) {
// Warning: amount_success and deposit_amount_success have true as default for // Warning: amount_success and deposit_amount_success have true as default for
@@ -508,14 +508,32 @@ func (hdb *HistoryDB) GetTxsAPI(
nextIsAnd = true nextIsAnd = true
} }
// idx filter // idx filter
if idx != nil { if fromIdx != nil && toIdx != nil {
if nextIsAnd { if nextIsAnd {
queryStr += "AND " queryStr += "AND "
} else { } else {
queryStr += "WHERE " queryStr += "WHERE "
} }
queryStr += "(tx.effective_from_idx = ? OR tx.to_idx = ?) " queryStr += "(tx.effective_from_idx = ? "
args = append(args, idx, idx) queryStr += "OR tx.to_idx = ?) "
args = append(args, fromIdx, toIdx)
nextIsAnd = true
} else if fromIdx != nil {
if nextIsAnd {
queryStr += "AND "
} else {
queryStr += "WHERE "
}
queryStr += "tx.effective_from_idx = ? "
nextIsAnd = true
} else if toIdx != nil {
if nextIsAnd {
queryStr += "AND "
} else {
queryStr += "WHERE "
}
queryStr += "tx.to_idx = ? "
args = append(args, toIdx)
nextIsAnd = true nextIsAnd = true
} }
// batchNum filter // batchNum filter

View File

@@ -1,24 +1,3 @@
/*
Package historydb is responsible for storing and retrieving the historic data of the Hermez network.
It's mostly but not exclusively used by the API and the synchronizer.
Apart from the logic defined in this package, it's important to notice that there are some triggers defined in the
migration files that have to be taken into consideration to understanding the results of some queries. This is especially true
for reorgs: all the data is directly or indirectly related to a block, this makes handling reorgs as easy as deleting the
reorged blocks from the block table, and all related items will be dropped in cascade. This is not the only case, in general
functions defined in this package that get affected somehow by the SQL level defined logic has a special mention on the function description.
Some of the database tooling used in this package such as meddler and migration tools is explained in the db package.
This package is spitted in different files following these ideas:
- historydb.go: constructor and functions used by packages other than the api.
- apiqueries.go: functions used by the API, the queries implemented in this functions use a semaphore
to restrict the maximum concurrent connections to the database.
- views.go: structs used to retrieve/store data from/to the database. When possible, the common structs are used, however
most of the time there is no 1:1 relation between the struct fields and the tables of the schema, especially when joining tables.
In some cases, some of the structs defined in this file also include custom Marshallers to easily match the expected api formats.
- nodeinfo.go: used to handle the interfaces and structs that allow communication across running in different machines/process but sharing the same database.
*/
package historydb package historydb
import ( import (

View File

@@ -127,3 +127,57 @@ func (l2db *L2DB) GetTxAPI(txID common.TxID) (*PoolTxAPI, error) {
txID, txID,
)) ))
} }
// GetPoolTxs return Txs from the pool
func (l2db *L2DB) GetPoolTxs(fromIdx, toIdx *common.Idx, state *common.PoolL2TxState) ([]*PoolTxAPI, error) {
cancel, err := l2db.apiConnCon.Acquire()
defer cancel()
if err != nil {
return nil, tracerr.Wrap(err)
}
defer l2db.apiConnCon.Release()
// Apply filters
nextIsAnd := false
queryStr := selectPoolTxAPI
var args []interface{}
if state != nil {
queryStr += "WHERE state = ? "
args = append(args, state)
nextIsAnd = true
}
if fromIdx != nil && toIdx != nil {
if nextIsAnd {
queryStr += "AND ("
} else {
queryStr += "WHERE ("
}
queryStr += "tx_pool.from_idx = ? "
queryStr += "OR tx_pool.to_idx = ?) "
args = append(args, fromIdx, toIdx)
} else if fromIdx != nil {
if nextIsAnd {
queryStr += "AND "
} else {
queryStr += "WHERE "
}
queryStr += "tx_pool.from_idx = ? "
args = append(args, fromIdx)
} else if toIdx != nil {
if nextIsAnd {
queryStr += "AND "
} else {
queryStr += "WHERE "
}
queryStr += "tx_pool.to_idx = ? "
args = append(args, toIdx)
}
queryStr += "AND NOT external_delete;"
query := l2db.dbRead.Rebind(queryStr)
txs := []*PoolTxAPI{}
err = meddler.QueryAll(
l2db.dbRead, &txs,
query,
args...)
return txs, tracerr.Wrap(err)
}

View File

@@ -1,20 +1,3 @@
/*
Package l2db is responsible for storing and retrieving the data received by the coordinator through the api.
Note that this data will be different for each coordinator in the network, as this represents the L2 information.
The data managed by this package is fundamentally PoolL2Tx and AccountCreationAuth. All this data come from
the API sent by clients and is used by the txselector to decide which transactions are selected to forge a batch.
Some of the database tooling used in this package such as meddler and migration tools is explained in the db package.
This package is spitted in different files following these ideas:
- l2db.go: constructor and functions used by packages other than the api.
- apiqueries.go: functions used by the API, the queries implemented in this functions use a semaphore
to restrict the maximum concurrent connections to the database.
- views.go: structs used to retrieve/store data from/to the database. When possible, the common structs are used, however
most of the time there is no 1:1 relation between the struct fields and the tables of the schema, especially when joining tables.
In some cases, some of the structs defined in this file also include custom Marshallers to easily match the expected api formats.
*/
package l2db package l2db
import ( import (

View File

@@ -311,6 +311,28 @@ func TestGetPending(t *testing.T) {
} }
} }
func TestL2DB_GetPoolTxs(t *testing.T) {
err := prepareHistoryDB(historyDB)
if err != nil {
log.Error("Error prepare historyDB", err)
}
poolL2Txs, err := generatePoolL2Txs()
require.NoError(t, err)
state := common.PoolL2TxState("pend")
idx := common.Idx(256)
var pendingTxs []*common.PoolL2Tx
for i := range poolL2Txs {
if poolL2Txs[i].FromIdx == idx || poolL2Txs[i].ToIdx == idx {
err := l2DB.AddTxTest(&poolL2Txs[i])
require.NoError(t, err)
pendingTxs = append(pendingTxs, &poolL2Txs[i])
}
}
fetchedTxs, err := l2DBWithACC.GetPoolTxs(&idx, &idx, &state)
require.NoError(t, err)
assert.Equal(t, len(pendingTxs), len(fetchedTxs))
}
func TestStartForging(t *testing.T) { func TestStartForging(t *testing.T) {
// Generate txs // Generate txs
var fakeBatchNum common.BatchNum = 33 var fakeBatchNum common.BatchNum = 33

View File

@@ -1,10 +1,3 @@
/*
Package db have some common utilities shared by db/l2db and db/historydb, the most relevant ones are:
- SQL connection utilities
- Managing the SQL schema: this is done using migration files placed under db/migrations. The files are executed by
order of the file name.
- Custom meddlers: used to easily transform struct <==> table
*/
package db package db
import ( import (

View File

@@ -245,15 +245,15 @@ func (c *EthereumClient) EthBlockByNumber(ctx context.Context, number int64) (*c
if number == -1 { if number == -1 {
blockNum = nil blockNum = nil
} }
block, err := c.client.BlockByNumber(ctx, blockNum) header, err := c.client.HeaderByNumber(ctx, blockNum)
if err != nil { if err != nil {
return nil, tracerr.Wrap(err) return nil, tracerr.Wrap(err)
} }
b := &common.Block{ b := &common.Block{
Num: block.Number().Int64(), Num: header.Number.Int64(),
Timestamp: time.Unix(int64(block.Time()), 0), Timestamp: time.Unix(int64(header.Time), 0),
ParentHash: block.ParentHash(), ParentHash: header.ParentHash,
Hash: block.Hash(), Hash: header.Hash(),
} }
return b, nil return b, nil
} }