mirror of
https://github.com/arnaucube/hermez-node.git
synced 2026-02-07 11:26:44 +01:00
Merge pull request #213 from hermeznetwork/feature/api-coordinators
Coordinators methods for API
This commit is contained in:
@@ -71,7 +71,7 @@ func SetAPIEndpoints(
|
||||
server.GET("/tokens/:id", getToken)
|
||||
server.GET("/recommendedFee", getRecommendedFee)
|
||||
server.GET("/coordinators", getCoordinators)
|
||||
server.GET("/coordinators/:forgerAddr", getCoordinator)
|
||||
server.GET("/coordinators/:bidderAddr", getCoordinator)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -41,6 +41,7 @@ type testCommon struct {
|
||||
blocks []common.Block
|
||||
tokens []tokenAPI
|
||||
batches []common.Batch
|
||||
coordinators []coordinatorAPI
|
||||
usrAddr string
|
||||
usrBjj string
|
||||
accs []common.Account
|
||||
@@ -622,11 +623,26 @@ func TestMain(m *testing.M) {
|
||||
poolTxsToSend = append(poolTxsToSend, genSendTx)
|
||||
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
|
||||
tc = testCommon{
|
||||
blocks: blocks,
|
||||
tokens: tokensUSD,
|
||||
batches: batches,
|
||||
coordinators: apiCoordinators,
|
||||
usrAddr: ethAddrToHez(usrAddr),
|
||||
usrBjj: bjjToString(usrBjj),
|
||||
accs: accs,
|
||||
@@ -1250,6 +1266,59 @@ func assertPoolTx(t *testing.T, expected, actual sendPoolTx) {
|
||||
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(
|
||||
path, order string,
|
||||
iterStruct db.Paginationer,
|
||||
|
||||
@@ -211,7 +211,6 @@ func historyExitsToAPI(dbExits []historydb.HistoryExit) []exitAPI {
|
||||
}
|
||||
|
||||
// Tokens
|
||||
|
||||
type tokensAPI struct {
|
||||
Tokens []tokenAPI `json:"tokens"`
|
||||
Pagination *db.Pagination `json:"pagination"`
|
||||
@@ -600,3 +599,45 @@ func poolL2TxReadToSend(dbTx *l2db.PoolL2TxRead) *sendPoolTx {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -23,6 +24,11 @@ const (
|
||||
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) {
|
||||
|
||||
}
|
||||
@@ -285,11 +291,49 @@ func getRecommendedFee(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) {
|
||||
// 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) {
|
||||
|
||||
@@ -310,3 +310,12 @@ func hezStringToBJJ(bjjStr, name string) (*babyjub.PublicKey, error) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -23,6 +23,13 @@ func (qp *queryParser) Query(query string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (qp *queryParser) Param(param string) string {
|
||||
if val, ok := qp.m[param]; ok {
|
||||
return val
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func TestParseQueryUint(t *testing.T) {
|
||||
name := "foo"
|
||||
c := &queryParser{}
|
||||
@@ -295,3 +302,23 @@ func TestParseTokenFilters(t *testing.T) {
|
||||
assert.Equal(t, symbolsArray, symbolsParse)
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -1162,7 +1162,7 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error500'
|
||||
'/coordinators/{forgerAddr}':
|
||||
'/coordinators/{bidderAddr}':
|
||||
get:
|
||||
tags:
|
||||
- Hermez status
|
||||
@@ -1170,7 +1170,7 @@ paths:
|
||||
description: Get the information of a coordinator.
|
||||
operationId: getCoordinator
|
||||
parameters:
|
||||
- name: forgerAddr
|
||||
- name: bidderAddr
|
||||
in: path
|
||||
description: Coordinator identifier
|
||||
required: true
|
||||
@@ -1807,9 +1807,11 @@ components:
|
||||
Coordinator:
|
||||
type: object
|
||||
properties:
|
||||
itemId:
|
||||
$ref: '#/components/schemas/ItemId'
|
||||
forgerAddr:
|
||||
$ref: '#/components/schemas/EthereumAddress'
|
||||
withdrawAddr:
|
||||
bidderAddr:
|
||||
$ref: '#/components/schemas/EthereumAddress'
|
||||
URL:
|
||||
$ref: '#/components/schemas/URL'
|
||||
@@ -1818,6 +1820,13 @@ components:
|
||||
- $ref: '#/components/schemas/EthBlockNum'
|
||||
- description: Ethereum block in which the coordinator registered into the network.
|
||||
- example: 5735943738
|
||||
additionalProperties: false
|
||||
required:
|
||||
- itemId
|
||||
- forgerAddr
|
||||
- bidderAddr
|
||||
- URL
|
||||
- ethereumBlock
|
||||
Coordinators:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1828,6 +1837,10 @@ components:
|
||||
$ref: '#/components/schemas/Coordinator'
|
||||
pagination:
|
||||
$ref: '#/components/schemas/PaginationInfo'
|
||||
additionalProperties: false
|
||||
required:
|
||||
- coordinators
|
||||
- pagination
|
||||
Bid:
|
||||
type: object
|
||||
description: Tokens placed in an auction by a coordinator to gain the right to forge batches during a specific slot.
|
||||
|
||||
@@ -915,3 +915,53 @@ func (hdb *HistoryDB) AddBlockSCData(blockData *BlockData) (err error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -118,3 +118,16 @@ type HistoryExit struct {
|
||||
TokenUSD *float64 `meddler:"usd"`
|
||||
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"`
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@ CREATE TABLE block (
|
||||
);
|
||||
|
||||
CREATE TABLE coordinator (
|
||||
item_id SERIAL PRIMARY KEY,
|
||||
bidder_addr BYTEA NOT NULL,
|
||||
forger_addr BYTEA NOT NULL,
|
||||
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 (
|
||||
|
||||
Reference in New Issue
Block a user