Browse Source

Merge pull request #213 from hermeznetwork/feature/api-coordinators

Coordinators methods for API
feature/sql-semaphore1
a_bennassar 4 years ago
committed by GitHub
parent
commit
9d74a8e9bd
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 273 additions and 7 deletions
  1. +1
    -1
      api/api.go
  2. +69
    -0
      api/api_test.go
  3. +42
    -1
      api/dbtoapistructs.go
  4. +44
    -0
      api/handlers.go
  5. +9
    -0
      api/parsers.go
  6. +27
    -0
      api/parsers_test.go
  7. +16
    -3
      api/swagger.yml
  8. +50
    -0
      db/historydb/historydb.go
  9. +13
    -0
      db/historydb/views.go
  10. +2
    -2
      db/migrations/0001.sql

+ 1
- 1
api/api.go

@ -71,7 +71,7 @@ func SetAPIEndpoints(
server.GET("/tokens/:id", getToken) server.GET("/tokens/:id", getToken)
server.GET("/recommendedFee", getRecommendedFee) server.GET("/recommendedFee", getRecommendedFee)
server.GET("/coordinators", getCoordinators) server.GET("/coordinators", getCoordinators)
server.GET("/coordinators/:forgerAddr", getCoordinator)
server.GET("/coordinators/:bidderAddr", getCoordinator)
} }
return nil return nil

+ 69
- 0
api/api_test.go

@ -41,6 +41,7 @@ type testCommon struct {
blocks []common.Block blocks []common.Block
tokens []tokenAPI tokens []tokenAPI
batches []common.Batch batches []common.Batch
coordinators []coordinatorAPI
usrAddr string usrAddr string
usrBjj string usrBjj string
accs []common.Account accs []common.Account
@ -622,11 +623,26 @@ func TestMain(m *testing.M) {
poolTxsToSend = append(poolTxsToSend, genSendTx) poolTxsToSend = append(poolTxsToSend, genSendTx)
poolTxsToReceive = append(poolTxsToReceive, genReceiveTx) poolTxsToReceive = append(poolTxsToReceive, genReceiveTx)
} }
// Coordinators
const nCoords = 10
coords := test.GenCoordinators(nCoords, blocks)
err = hdb.AddCoordinators(coords)
if err != nil {
panic(err)
}
fromItem := uint(0)
limit := uint(99999)
coordinators, _, err := hdb.GetCoordinators(&fromItem, &limit, historydb.OrderAsc)
apiCoordinators := coordinatorsToAPI(coordinators)
if err != nil {
panic(err)
}
// Set testCommon // Set testCommon
tc = testCommon{ tc = testCommon{
blocks: blocks, blocks: blocks,
tokens: tokensUSD, tokens: tokensUSD,
batches: batches, batches: batches,
coordinators: apiCoordinators,
usrAddr: ethAddrToHez(usrAddr), usrAddr: ethAddrToHez(usrAddr),
usrBjj: bjjToString(usrBjj), usrBjj: bjjToString(usrBjj),
accs: accs, accs: accs,
@ -1250,6 +1266,59 @@ func assertPoolTx(t *testing.T, expected, actual sendPoolTx) {
assert.Equal(t, expected, actual) assert.Equal(t, expected, actual)
} }
func TestGetCoordinators(t *testing.T) {
endpoint := apiURL + "coordinators"
fetchedCoordinators := []coordinatorAPI{}
appendIter := func(intr interface{}) {
for i := 0; i < len(intr.(*coordinatorsAPI).Coordinators); i++ {
tmp, err := copystructure.Copy(intr.(*coordinatorsAPI).Coordinators[i])
if err != nil {
panic(err)
}
fetchedCoordinators = append(fetchedCoordinators, tmp.(coordinatorAPI))
}
}
limit := 5
path := fmt.Sprintf("%s?limit=%d&fromItem=", endpoint, limit)
err := doGoodReqPaginated(path, historydb.OrderAsc, &coordinatorsAPI{}, appendIter)
assert.NoError(t, err)
assert.Equal(t, tc.coordinators, fetchedCoordinators)
// Reverse Order
reversedCoordinators := []coordinatorAPI{}
appendIter = func(intr interface{}) {
for i := 0; i < len(intr.(*coordinatorsAPI).Coordinators); i++ {
tmp, err := copystructure.Copy(intr.(*coordinatorsAPI).Coordinators[i])
if err != nil {
panic(err)
}
reversedCoordinators = append(reversedCoordinators, tmp.(coordinatorAPI))
}
}
err = doGoodReqPaginated(path, historydb.OrderDesc, &coordinatorsAPI{}, appendIter)
assert.NoError(t, err)
for i := 0; i < len(fetchedCoordinators); i++ {
assert.Equal(t, reversedCoordinators[i], fetchedCoordinators[len(fetchedCoordinators)-1-i])
}
// Test GetCoordinator
path = fmt.Sprintf("%s/%s", endpoint, fetchedCoordinators[2].Forger.String())
coordinator := coordinatorAPI{}
assert.NoError(t, doGoodReq("GET", path, nil, &coordinator))
assert.Equal(t, fetchedCoordinators[2], coordinator)
// 400
path = fmt.Sprintf("%s/0x001", endpoint)
err = doBadReq("GET", path, nil, 400)
assert.NoError(t, err)
// 404
path = fmt.Sprintf("%s/0xaa942cfcd25ad4d90a62358b0dd84f33b398262a", endpoint)
err = doBadReq("GET", path, nil, 404)
assert.NoError(t, err)
}
func doGoodReqPaginated( func doGoodReqPaginated(
path, order string, path, order string,
iterStruct db.Paginationer, iterStruct db.Paginationer,

+ 42
- 1
api/dbtoapistructs.go

@ -211,7 +211,6 @@ func historyExitsToAPI(dbExits []historydb.HistoryExit) []exitAPI {
} }
// Tokens // Tokens
type tokensAPI struct { type tokensAPI struct {
Tokens []tokenAPI `json:"tokens"` Tokens []tokenAPI `json:"tokens"`
Pagination *db.Pagination `json:"pagination"` Pagination *db.Pagination `json:"pagination"`
@ -600,3 +599,45 @@ func poolL2TxReadToSend(dbTx *l2db.PoolL2TxRead) *sendPoolTx {
} }
return tx return tx
} }
// Coordinators
type coordinatorAPI struct {
ItemID int `json:"itemId"`
Bidder ethCommon.Address `json:"bidderAddr"`
Forger ethCommon.Address `json:"forgerAddr"`
EthBlockNum int64 `json:"ethereumBlock"`
URL string `json:"URL"`
}
type coordinatorsAPI struct {
Coordinators []coordinatorAPI `json:"coordinators"`
Pagination *db.Pagination `json:"pagination"`
}
func (t *coordinatorsAPI) GetPagination() *db.Pagination {
if t.Coordinators[0].ItemID < t.Coordinators[len(t.Coordinators)-1].ItemID {
t.Pagination.FirstReturnedItem = t.Coordinators[0].ItemID
t.Pagination.LastReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
} else {
t.Pagination.LastReturnedItem = t.Coordinators[0].ItemID
t.Pagination.FirstReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
}
return t.Pagination
}
func (t *coordinatorsAPI) Len() int { return len(t.Coordinators) }
func coordinatorsToAPI(dbCoordinators []historydb.HistoryCoordinator) []coordinatorAPI {
apiCoordinators := []coordinatorAPI{}
for i := 0; i < len(dbCoordinators); i++ {
apiCoordinators = append(apiCoordinators, coordinatorAPI{
ItemID: dbCoordinators[i].ItemID,
Bidder: dbCoordinators[i].Bidder,
Forger: dbCoordinators[i].Forger,
EthBlockNum: dbCoordinators[i].EthBlockNum,
URL: dbCoordinators[i].URL,
})
}
return apiCoordinators
}

+ 44
- 0
api/handlers.go

@ -2,6 +2,7 @@ package api
import ( import (
"database/sql" "database/sql"
"errors"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -23,6 +24,11 @@ const (
maxUint32 = 4294967295 maxUint32 = 4294967295
) )
var (
// ErrNillBidderAddr is used when a nil bidderAddr is received in the getCoordinator method
ErrNillBidderAddr = errors.New("biderAddr can not be nil")
)
func postAccountCreationAuth(c *gin.Context) { func postAccountCreationAuth(c *gin.Context) {
} }
@ -285,11 +291,49 @@ func getRecommendedFee(c *gin.Context) {
} }
func getCoordinators(c *gin.Context) { func getCoordinators(c *gin.Context) {
// Pagination
fromItem, order, limit, err := parsePagination(c)
if err != nil {
retBadReq(err, c)
return
}
// Fetch coordinators from historyDB
coordinators, pagination, err := h.GetCoordinators(fromItem, limit, order)
if err != nil {
retSQLErr(err, c)
return
}
// Build succesfull response
apiCoordinators := coordinatorsToAPI(coordinators)
c.JSON(http.StatusOK, &coordinatorsAPI{
Coordinators: apiCoordinators,
Pagination: pagination,
})
} }
func getCoordinator(c *gin.Context) { func getCoordinator(c *gin.Context) {
// Get bidderAddr
const name = "bidderAddr"
bidderAddr, err := parseEthAddr(c, name)
if err != nil {
retBadReq(err, c)
return
} else if bidderAddr == nil {
retBadReq(ErrNillBidderAddr, c)
return
}
coordinator, err := h.GetCoordinator(*bidderAddr)
if err != nil {
retSQLErr(err, c)
return
}
apiCoordinator := coordinatorsToAPI([]historydb.HistoryCoordinator{*coordinator})
c.JSON(http.StatusOK, apiCoordinator[0])
} }
func retSQLErr(err error, c *gin.Context) { func retSQLErr(err error, c *gin.Context) {

+ 9
- 0
api/parsers.go

@ -310,3 +310,12 @@ func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
} }
return bjj, nil return bjj, nil
} }
func parseEthAddr(c paramer, name string) (*ethCommon.Address, error) {
addrStr := c.Param(name)
if addrStr == "" {
return nil, nil
}
var addr ethCommon.Address
err := addr.UnmarshalText([]byte(addrStr))
return &addr, err
}

+ 27
- 0
api/parsers_test.go

@ -23,6 +23,13 @@ func (qp *queryParser) Query(query string) string {
return "" return ""
} }
func (qp *queryParser) Param(param string) string {
if val, ok := qp.m[param]; ok {
return val
}
return ""
}
func TestParseQueryUint(t *testing.T) { func TestParseQueryUint(t *testing.T) {
name := "foo" name := "foo"
c := &queryParser{} c := &queryParser{}
@ -295,3 +302,23 @@ func TestParseTokenFilters(t *testing.T) {
assert.Equal(t, symbolsArray, symbolsParse) assert.Equal(t, symbolsArray, symbolsParse)
assert.Equal(t, nameValue, nameParse) assert.Equal(t, nameValue, nameParse)
} }
func TestParseEthAddr(t *testing.T) {
name := "forgerAddr"
c := &queryParser{}
c.m = make(map[string]string)
ethAddr := ethCommon.BigToAddress(big.NewInt(int64(123456)))
// Default
c.m[name] = ""
res, err := parseEthAddr(c, name)
assert.NoError(t, err)
assert.Nil(t, res)
// Incorrect
c.m[name] = "0x12345678"
_, err = parseEthAddr(c, name)
assert.Error(t, err)
// Correct
c.m[name] = ethAddr.String()
res, err = parseEthAddr(c, name)
assert.NoError(t, err)
assert.Equal(t, ethAddr, *res)
}

+ 16
- 3
api/swagger.yml

@ -1162,7 +1162,7 @@ paths:
application/json: application/json:
schema: schema:
$ref: '#/components/schemas/Error500' $ref: '#/components/schemas/Error500'
'/coordinators/{forgerAddr}':
'/coordinators/{bidderAddr}':
get: get:
tags: tags:
- Hermez status - Hermez status
@ -1170,7 +1170,7 @@ paths:
description: Get the information of a coordinator. description: Get the information of a coordinator.
operationId: getCoordinator operationId: getCoordinator
parameters: parameters:
- name: forgerAddr
- name: bidderAddr
in: path in: path
description: Coordinator identifier description: Coordinator identifier
required: true required: true
@ -1807,9 +1807,11 @@ components:
Coordinator: Coordinator:
type: object type: object
properties: properties:
itemId:
$ref: '#/components/schemas/ItemId'
forgerAddr: forgerAddr:
$ref: '#/components/schemas/EthereumAddress' $ref: '#/components/schemas/EthereumAddress'
withdrawAddr:
bidderAddr:
$ref: '#/components/schemas/EthereumAddress' $ref: '#/components/schemas/EthereumAddress'
URL: URL:
$ref: '#/components/schemas/URL' $ref: '#/components/schemas/URL'
@ -1818,6 +1820,13 @@ components:
- $ref: '#/components/schemas/EthBlockNum' - $ref: '#/components/schemas/EthBlockNum'
- description: Ethereum block in which the coordinator registered into the network. - description: Ethereum block in which the coordinator registered into the network.
- example: 5735943738 - example: 5735943738
additionalProperties: false
required:
- itemId
- forgerAddr
- bidderAddr
- URL
- ethereumBlock
Coordinators: Coordinators:
type: object type: object
properties: properties:
@ -1828,6 +1837,10 @@ components:
$ref: '#/components/schemas/Coordinator' $ref: '#/components/schemas/Coordinator'
pagination: pagination:
$ref: '#/components/schemas/PaginationInfo' $ref: '#/components/schemas/PaginationInfo'
additionalProperties: false
required:
- coordinators
- pagination
Bid: Bid:
type: object type: object
description: Tokens placed in an auction by a coordinator to gain the right to forge batches during a specific slot. description: Tokens placed in an auction by a coordinator to gain the right to forge batches during a specific slot.

+ 50
- 0
db/historydb/historydb.go

@ -915,3 +915,53 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
return txn.Commit() return txn.Commit()
} }
// GetCoordinator returns a coordinator by its bidderAddr
func (hdb *HistoryDB) GetCoordinator(bidderAddr ethCommon.Address) (*HistoryCoordinator, error) {
coordinator := &HistoryCoordinator{}
err := meddler.QueryRow(
hdb.db, coordinator, `SELECT * FROM coordinator WHERE bidder_addr = $1;`, bidderAddr,
)
return coordinator, err
}
// GetCoordinators returns a list of coordinators from the DB and pagination info
func (hdb *HistoryDB) GetCoordinators(fromItem, limit *uint, order string) ([]HistoryCoordinator, *db.Pagination, error) {
var query string
var args []interface{}
queryStr := `SELECT coordinator.*,
COUNT(*) OVER() AS total_items, MIN(coordinator.item_id) OVER() AS first_item, MAX(coordinator.item_id) OVER() AS last_item
FROM coordinator `
// Apply filters
if fromItem != nil {
queryStr += "WHERE "
if order == OrderAsc {
queryStr += "coordinator.item_id >= ? "
} else {
queryStr += "coordinator.item_id <= ? "
}
args = append(args, fromItem)
}
// pagination
queryStr += "ORDER BY coordinator.item_id "
if order == OrderAsc {
queryStr += " ASC "
} else {
queryStr += " DESC "
}
queryStr += fmt.Sprintf("LIMIT %d;", *limit)
query = hdb.db.Rebind(queryStr)
coordinators := []*HistoryCoordinator{}
if err := meddler.QueryAll(hdb.db, &coordinators, query, args...); err != nil {
return nil, nil, err
}
if len(coordinators) == 0 {
return nil, nil, sql.ErrNoRows
}
return db.SlicePtrsToSlice(coordinators).([]HistoryCoordinator), &db.Pagination{
TotalItems: coordinators[0].TotalItems,
FirstItem: coordinators[0].FirstItem,
LastItem: coordinators[0].LastItem,
}, nil
}

+ 13
- 0
db/historydb/views.go

@ -118,3 +118,16 @@ type HistoryExit struct {
TokenUSD *float64 `meddler:"usd"` TokenUSD *float64 `meddler:"usd"`
TokenUSDUpdate *time.Time `meddler:"usd_update"` TokenUSDUpdate *time.Time `meddler:"usd_update"`
} }
// HistoryCoordinator is a representation of a coordinator with additional information
// required by the API
type HistoryCoordinator struct {
ItemID int `meddler:"item_id"`
Bidder ethCommon.Address `meddler:"bidder_addr"`
Forger ethCommon.Address `meddler:"forger_addr"`
EthBlockNum int64 `meddler:"eth_block_num"`
URL string `meddler:"url"`
TotalItems int `meddler:"total_items"`
FirstItem int `meddler:"first_item"`
LastItem int `meddler:"last_item"`
}

+ 2
- 2
db/migrations/0001.sql

@ -8,11 +8,11 @@ CREATE TABLE block (
); );
CREATE TABLE coordinator ( CREATE TABLE coordinator (
item_id SERIAL PRIMARY KEY,
bidder_addr BYTEA NOT NULL, bidder_addr BYTEA NOT NULL,
forger_addr BYTEA NOT NULL, forger_addr BYTEA NOT NULL,
eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE, eth_block_num BIGINT NOT NULL REFERENCES block (eth_block_num) ON DELETE CASCADE,
url VARCHAR(200) NOT NULL,
PRIMARY KEY (bidder_addr, eth_block_num)
url VARCHAR(200) NOT NULL
); );
CREATE TABLE batch ( CREATE TABLE batch (

Loading…
Cancel
Save