arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
Coordinators methods for API
ToniRamirezM toni@iden3.com
Wed, 21 Oct 2020 12:24:58 +0200
commit

f314498a264d9c183e96e4e94be22463109cf4be

parent

8047582de974c9c7d9b923b4bff26d6671718313

M api/api.go → api/api.go

@@ -71,7 +71,7 @@ server.GET("/tokens", getTokens)

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
M api/api_test.go → api/api_test.go

@@ -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 @@ }

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,

@@ -1248,6 +1264,59 @@ assert.Equal(t, expected.Token.USDUpdate.Unix(), actual.Token.USDUpdate.Unix())

expected.Token.USDUpdate = actual.Token.USDUpdate } 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(
M api/dbtoapistructs.go → api/dbtoapistructs.go

@@ -211,7 +211,6 @@ return apiExits

} // Tokens - type tokensAPI struct { Tokens []tokenAPI `json:"tokens"` Pagination *db.Pagination `json:"pagination"`

@@ -600,3 +599,45 @@ tx.RqAmount = &rqAmount

} 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 +}
M api/handlers.go → api/handlers.go

@@ -2,6 +2,7 @@ package api

import ( "database/sql" + "errors" "net/http" "github.com/gin-gonic/gin"

@@ -21,6 +22,11 @@ dfltLimit uint = 20

// 2^32 -1 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 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) {
M api/parsers.go → api/parsers.go

@@ -310,3 +310,12 @@ name, err.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 +}
M api/parsers_test.go → api/parsers_test.go

@@ -23,6 +23,13 @@ }

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 @@ var symbolsArray []string = []string{"1", "2", "3"}

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) +}
M api/swagger.yml → api/swagger.yml

@@ -1162,7 +1162,7 @@ content:

application/json: schema: $ref: '#/components/schemas/Error500' - '/coordinators/{forgerAddr}': + '/coordinators/{bidderAddr}': get: tags: - Hermez status

@@ -1170,7 +1170,7 @@ summary: Get the information of a coordinator.

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 @@ $ref: '#/components/schemas/PaginationInfo'

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 @@ allOf:

- $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 @@ items:

$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.
M db/historydb/historydb.go → db/historydb/historydb.go

@@ -915,3 +915,53 @@ }

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 +}
M db/historydb/views.go → db/historydb/views.go

@@ -118,3 +118,16 @@ TokenDecimals uint64 `meddler:"decimals"`

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"` +}
M db/migrations/0001.sql → db/migrations/0001.sql

@@ -8,11 +8,11 @@ hash BYTEA NOT NULL

); 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 (