[NOD-307] Implement get blocks for api server (#405)

* [NOD-307] Implement API-Server GET /blocks

* [NOD-307] Implement API-Server GET /blocks

* [NOD-307] Add comments to exported constants

* [NOD-307] Flatten GET query values and check that 'order' value is valid

* [NOD-307] Validate order values in GetBlocksHandler

* [NOD-307] Add convertQueryParamToInt function
This commit is contained in:
Ori Newman 2019-09-16 16:53:57 +03:00 committed by Svarog
parent 31ccedf136
commit e81ac5f19e
3 changed files with 122 additions and 31 deletions

View File

@ -11,6 +11,20 @@ import (
"github.com/daglabs/btcd/util/daghash"
)
const (
// OrderAscending is parameter that can be used
// in a get list handler to get a list ordered
// in an ascending order.
OrderAscending = "asc"
// OrderDescending is parameter that can be used
// in a get list handler to get a list ordered
// in an ascending order.
OrderDescending = "desc"
)
const maxGetBlocksLimit = 100
// GetBlockByHashHandler returns a block by a given hash.
func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) {
if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize {
@ -30,3 +44,32 @@ func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError)
}
return convertBlockModelToBlockResponse(block), nil
}
// GetBlocksHandler searches for all blocks
func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
if limit > maxGetBlocksLimit {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
}
blocks := []*models.Block{}
db, err := database.DB()
if err != nil {
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
query := db.
Limit(limit).
Offset(skip).
Preload("AcceptingBlock")
if order == OrderAscending {
query = query.Order("`id` ASC")
} else if order == OrderDescending {
query = query.Order("`id` DESC")
} else {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid order", order))
}
query.Find(&blocks)
blockResponses := make([]*blockResponse, len(blocks))
for i, block := range blocks {
blockResponses[i] = convertBlockModelToBlockResponse(block)
}
return blockResponses, nil
}

View File

@ -12,7 +12,7 @@ import (
"github.com/jinzhu/gorm"
)
const maximumGetTransactionsLimit = 1000
const maxGetTransactionsLimit = 1000
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
@ -59,9 +59,9 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerErro
// GetTransactionsByAddressHandler searches for all transactions
// where the given address is either an input or an output.
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
if limit > maximumGetTransactionsLimit {
if limit > maxGetTransactionsLimit {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The maximum allowed value for the limit is %d", maximumGetTransactionsLimit))
fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
}
db, err := database.DB()

View File

@ -21,17 +21,27 @@ const (
const (
queryParamSkip = "skip"
queryParamLimit = "limit"
queryParamOrder = "order"
)
const defaultGetTransactionsLimit = 100
const (
defaultGetTransactionsLimit = 100
defaultGetBlocksLimit = 25
defaultGetBlocksOrder = controllers.OrderAscending
)
func makeHandler(
handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string][]string) (
handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (
interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
ctx := utils.ToAPIServerContext(r.Context())
response, hErr := handler(ctx, mux.Vars(r), r.URL.Query())
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
if hErr != nil {
sendErr(ctx, w, hErr)
return
}
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams)
if hErr != nil {
sendErr(ctx, w, hErr)
return
@ -40,6 +50,18 @@ func makeHandler(
}
}
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) {
flattenedMap := make(map[string]string)
for param, valuesSlice := range queryParams {
if len(valuesSlice) > 1 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got multiple values", param))
}
flattenedMap[param] = valuesSlice[0]
}
return flattenedMap, nil
}
type clientError struct {
ErrorCode int `json:"errorCode"`
ErrorMessage string `json:"errorMessage"`
@ -66,7 +88,7 @@ func sendJSONResponse(w http.ResponseWriter, response interface{}) {
}
}
func mainHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func mainHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return "API server is running", nil
}
@ -98,41 +120,48 @@ func addRoutes(router *mux.Router) {
makeHandler(getBlockByHashHandler)).
Methods("GET")
router.HandleFunc(
"/blocks",
makeHandler(getBlocksHandler)).
Methods("GET")
router.HandleFunc(
"/fee-estimates",
makeHandler(getFeeEstimatesHandler)).
Methods("GET")
}
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) {
if _, ok := queryParams[param]; ok {
intValue, err := strconv.Atoi(queryParams[param])
if err != nil {
return 0, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err))
}
return intValue, nil
}
return defaultValue, nil
}
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID])
}
func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func getTransactionByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
}
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string][]string) (interface{}, *utils.HandlerError) {
skip := 0
limit := defaultGetTransactionsLimit
if len(queryParams[queryParamSkip]) > 1 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got an array", queryParamSkip))
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
}
if len(queryParams[queryParamSkip]) == 1 {
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
if hErr != nil {
return nil, hErr
}
if _, ok := queryParams[queryParamLimit]; ok {
var err error
skip, err = strconv.Atoi(queryParams[queryParamSkip][0])
if err != nil {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamSkip, err))
}
}
if len(queryParams[queryParamLimit]) > 1 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got an array", queryParamLimit))
}
if len(queryParams[queryParamLimit]) == 1 {
var err error
skip, err = strconv.Atoi(queryParams[queryParamLimit][0])
skip, err = strconv.Atoi(queryParams[queryParamLimit])
if err != nil {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
}
@ -140,14 +169,33 @@ func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[
return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit))
}
func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func getUTXOsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress])
}
func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func getBlockByHashHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash])
}
func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string][]string) (interface{}, *utils.HandlerError) {
func getFeeEstimatesHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
return controllers.GetFeeEstimatesHandler()
}
func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil {
return nil, hErr
}
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
if hErr != nil {
return nil, hErr
}
order := defaultGetBlocksOrder
if orderParamValue, ok := queryParams[queryParamOrder]; ok {
if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
}
order = orderParamValue
}
return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit))
}