Compare commits

...

13 Commits

Author SHA1 Message Date
Oleksandr Brezhniev
9e40265226 Merge pull request #674 from hermeznetwork/feature/apiWithoutError
added checker for version in no route api
2021-03-31 23:47:06 +03:00
Mikelle
7f971fb72b changed const declaration in api_test.go 2021-03-31 19:11:07 +03:00
Mikelle
e9b80d0c6d Merge branch 'develop' into feature/apiWithoutError
# Conflicts:
#	api/api.go
2021-03-31 19:02:44 +03:00
Danilo Pantani
2dba819696 Merge pull request #676 from hermeznetwork/feature/warnLogPriceUpdate
added checker and log.warn to updateMethodTypeStatic
2021-03-31 12:40:01 -03:00
Danilo Pantani
629c67a62e Merge pull request #675 from hermeznetwork/feature/configChainId
added chainId to config API
2021-03-31 12:39:39 -03:00
Eduard S
bb71a94e22 Merge pull request #688 from hermeznetwork/feature/txsel-l1frozenqueue-acccreation
TxSel avoid L1CoordTx for L2Tx that have a L1UserTx in the frozen queue
2021-03-31 13:25:24 +02:00
arnau
6074f2d7fb Merge pull request #683 from hermeznetwork/doc/api-and-sql-related
Add package level documentation for db, historydb, l2db, api, apitype…
2021-03-31 13:05:43 +02:00
arnaubennassar
4c99640b8c Add package level documentation for db, historydb, l2db and api/... 2021-03-31 11:14:49 +02:00
Mikelle
01ec1ca395 added test and move logic to api.NewAPI 2021-03-25 22:41:46 +03:00
Mikelle
561f491d53 added token symbol to UpdateMethodTypeStatic log 2021-03-25 21:42:50 +03:00
Mikelle
4dc44e70c4 added checker for version in no route api 2021-03-24 23:13:45 +03:00
Mikelle
7b6dd0899e added checker and log.warn to updateMethodTypeStatic 2021-03-24 22:36:07 +03:00
Mikelle
14ead3ddf1 added chainId to config API 2021-03-24 22:20:46 +03:00
15 changed files with 182 additions and 16 deletions

View File

@@ -21,7 +21,7 @@ func (a *API) postAccountCreationAuth(c *gin.Context) {
}
// API to common + verify signature
commonAuth := accountCreationAuthAPIToCommon(&apiAuth)
if !commonAuth.VerifySignature(a.chainID, a.hermezAddress) {
if !commonAuth.VerifySignature(a.cg.ChainID, a.hermezAddress) {
retBadReq(errors.New("invalid signature"), c)
return
}

View File

@@ -1,3 +1,27 @@
/*
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
import (
@@ -16,7 +40,6 @@ type API struct {
h *historydb.HistoryDB
cg *configAPI
l2 *l2db.L2DB
chainID uint16
hermezAddress ethCommon.Address
}
@@ -28,7 +51,6 @@ func NewAPI(
l2db *l2db.L2DB,
) (*API, error) {
// Check input
// TODO: is stateDB only needed for explorer endpoints or for both?
if coordinatorEndpoints && l2db == nil {
return nil, tracerr.Wrap(errors.New("cannot serve Coordinator endpoints without L2DB"))
}
@@ -45,9 +67,9 @@ func NewAPI(
RollupConstants: *newRollupConstants(consts.Rollup),
AuctionConstants: consts.Auction,
WDelayerConstants: consts.WDelayer,
ChainID: consts.ChainID,
},
l2: l2db,
chainID: consts.ChainID,
hermezAddress: consts.HermezAddress,
}
@@ -57,11 +79,13 @@ func NewAPI(
}
server.Use(middleware)
server.NoRoute(a.noRoute)
v1 := server.Group("/v1")
// Add coordinator endpoints
if coordinatorEndpoints {
// Account
// Account creation authorization
v1.POST("/account-creation-authorization", a.postAccountCreationAuth)
v1.GET("/account-creation-authorization/:hezEthereumAddress", a.getAccountCreationAuth)
// Transaction
@@ -79,17 +103,23 @@ func NewAPI(
// Transaction
v1.GET("/transactions-history", a.getHistoryTxs)
v1.GET("/transactions-history/:id", a.getHistoryTx)
// Status
// Batches
v1.GET("/batches", a.getBatches)
v1.GET("/batches/:batchNum", a.getBatch)
v1.GET("/full-batches/:batchNum", a.getFullBatch)
// Slots
v1.GET("/slots", a.getSlots)
v1.GET("/slots/:slotNum", a.getSlot)
// Bids
v1.GET("/bids", a.getBids)
// State
v1.GET("/state", a.getState)
// Config
v1.GET("/config", a.getConfig)
// Tokens
v1.GET("/tokens", a.getTokens)
v1.GET("/tokens/:id", a.getToken)
// Coordinators
v1.GET("/coordinators", a.getCoordinators)
}

View File

@@ -40,8 +40,11 @@ type Pendinger interface {
New() Pendinger
}
const apiPort = "4010"
const apiURL = "http://localhost:" + apiPort + "/v1/"
const (
apiPort = "4010"
apiIP = "http://localhost:"
apiURL = apiIP + apiPort + "/v1/"
)
var SetBlockchain = `
Type: Blockchain
@@ -215,6 +218,7 @@ func TestMain(m *testing.M) {
chainID := uint16(0)
_config := getConfigTest(chainID)
config = configAPI{
ChainID: chainID,
RollupConstants: *newRollupConstants(_config.RollupConstants),
AuctionConstants: _config.AuctionConstants,
WDelayerConstants: _config.WDelayerConstants,
@@ -846,6 +850,25 @@ func doBadReq(method, path string, reqBody io.Reader, expectedResponseCode int)
return swagger.ValidateResponse(ctx, responseValidationInput)
}
func doSimpleReq(method, endpoint string) (string, error) {
client := &http.Client{}
httpReq, err := http.NewRequest(method, endpoint, nil)
if err != nil {
return "", tracerr.Wrap(err)
}
resp, err := client.Do(httpReq)
if err != nil {
return "", tracerr.Wrap(err)
}
//nolint
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", tracerr.Wrap(err)
}
return string(body), nil
}
// test helpers
func getTimestamp(blockNum int64, blocks []common.Block) time.Time {

View File

@@ -1,3 +1,11 @@
/*
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
import (

View File

@@ -57,6 +57,7 @@ type Config struct {
}
type configAPI struct {
ChainID uint16 `json:"chainId"`
RollupConstants rollupConstants `json:"hermez"`
AuctionConstants common.AuctionConstants `json:"auction"`
WDelayerConstants common.WDelayerConstants `json:"withdrawalDelayer"`

21
api/noroute.go Normal file
View File

@@ -0,0 +1,21 @@
package api
import (
"net/http"
"regexp"
"github.com/gin-gonic/gin"
)
func (a *API) noRoute(c *gin.Context) {
matched, _ := regexp.MatchString(`^/v[0-9]+/`, c.Request.URL.Path)
if !matched {
c.JSON(http.StatusNotFound, gin.H{
"error": "Version not provided, please provide a valid version in the path such as v1",
})
return
}
c.JSON(http.StatusNotFound, gin.H{
"error": "404 page not found",
})
}

29
api/noroute_test.go Normal file
View File

@@ -0,0 +1,29 @@
package api
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNoRouteVersionNotProvided(t *testing.T) {
endpoint := apiIP + apiPort + "/"
// not using doGoodReq, bcs internally
// there is a method FindRoute that checks route and returns error
resp, err := doSimpleReq("GET", endpoint)
assert.NoError(t, err)
assert.Equal(t,
"{\"error\":\"Version not provided, please provide a valid version in the path such as v1\"}\n",
resp)
}
func TestNoRoute(t *testing.T) {
endpoint := apiURL
// not using doGoodReq, bcs internally
// there is a method FindRoute that checks route and returns error
resp, err := doSimpleReq("GET", endpoint)
assert.NoError(t, err)
assert.Equal(t,
"{\"error\":\"404 page not found\"}\n",
resp)
}

View File

@@ -1,3 +1,10 @@
/*
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
import (

View File

@@ -3040,10 +3040,15 @@ components:
- maxEmergencyModeTime
- hermezRollup
additionalProperties: false
chainId:
type: integer
description: Id of the chain
example: 27
required:
- hermez
- auction
- withdrawalDelayer
- chainId
additionalProperties: false
Error:
type: object

View File

@@ -187,7 +187,7 @@ func (a *API) verifyPoolL2TxWrite(txw *l2db.PoolL2TxWrite) error {
poolTx.TokenID, account.TokenID))
}
// Check signature
if !poolTx.VerifySignature(a.chainID, account.BJJ) {
if !poolTx.VerifySignature(a.cg.ChainID, account.BJJ) {
return tracerr.Wrap(errors.New("wrong signature"))
}
return nil

View File

@@ -1,3 +1,24 @@
/*
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
import (

View File

@@ -1,3 +1,20 @@
/*
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
import (

View File

@@ -1,3 +1,10 @@
/*
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
import (

View File

@@ -616,12 +616,6 @@ type NodeAPI struct { //nolint:golint
addr string
}
func handleNoRoute(c *gin.Context) {
c.JSON(http.StatusNotFound, gin.H{
"error": "404 page not found",
})
}
// NewNodeAPI creates a new NodeAPI (which internally calls api.NewAPI)
func NewNodeAPI(
addr string,
@@ -631,7 +625,6 @@ func NewNodeAPI(
l2db *l2db.L2DB,
) (*NodeAPI, error) {
engine := gin.Default()
engine.NoRoute(handleNoRoute)
engine.Use(cors.Default())
_api, err := api.NewAPI(
coordinatorEndpoints, explorerEndpoints,

View File

@@ -173,6 +173,10 @@ func (p *PriceUpdater) UpdatePrices(ctx context.Context) {
tokenPrice, err = p.getTokenPriceCoingecko(ctx, token.Addr)
case UpdateMethodTypeStatic:
tokenPrice = token.StaticValue
if tokenPrice == float64(0) {
log.Warn("token price is set to 0. Probably StaticValue is not put in the configuration file,",
"token", token.Symbol)
}
case UpdateMethodTypeIgnore:
continue
}