mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-15 18:46:39 +00:00
[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:
parent
31ccedf136
commit
e81ac5f19e
@ -11,6 +11,20 @@ import (
|
|||||||
"github.com/daglabs/btcd/util/daghash"
|
"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.
|
// GetBlockByHashHandler returns a block by a given hash.
|
||||||
func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) {
|
func GetBlockByHashHandler(blockHash string) (interface{}, *utils.HandlerError) {
|
||||||
if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize {
|
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
|
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
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
const maximumGetTransactionsLimit = 1000
|
const maxGetTransactionsLimit = 1000
|
||||||
|
|
||||||
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
|
// GetTransactionByIDHandler returns a transaction by a given transaction ID.
|
||||||
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
|
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
|
||||||
@ -59,9 +59,9 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerErro
|
|||||||
// GetTransactionsByAddressHandler searches for all transactions
|
// GetTransactionsByAddressHandler searches for all transactions
|
||||||
// where the given address is either an input or an output.
|
// where the given address is either an input or an output.
|
||||||
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
|
func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, *utils.HandlerError) {
|
||||||
if limit > maximumGetTransactionsLimit {
|
if limit > maxGetTransactionsLimit {
|
||||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
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()
|
db, err := database.DB()
|
||||||
|
@ -21,17 +21,27 @@ const (
|
|||||||
const (
|
const (
|
||||||
queryParamSkip = "skip"
|
queryParamSkip = "skip"
|
||||||
queryParamLimit = "limit"
|
queryParamLimit = "limit"
|
||||||
|
queryParamOrder = "order"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultGetTransactionsLimit = 100
|
const (
|
||||||
|
defaultGetTransactionsLimit = 100
|
||||||
|
defaultGetBlocksLimit = 25
|
||||||
|
defaultGetBlocksOrder = controllers.OrderAscending
|
||||||
|
)
|
||||||
|
|
||||||
func makeHandler(
|
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) {
|
interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
ctx := utils.ToAPIServerContext(r.Context())
|
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 {
|
if hErr != nil {
|
||||||
sendErr(ctx, w, hErr)
|
sendErr(ctx, w, hErr)
|
||||||
return
|
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 {
|
type clientError struct {
|
||||||
ErrorCode int `json:"errorCode"`
|
ErrorCode int `json:"errorCode"`
|
||||||
ErrorMessage string `json:"errorMessage"`
|
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
|
return "API server is running", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,41 +120,48 @@ func addRoutes(router *mux.Router) {
|
|||||||
makeHandler(getBlockByHashHandler)).
|
makeHandler(getBlockByHashHandler)).
|
||||||
Methods("GET")
|
Methods("GET")
|
||||||
|
|
||||||
|
router.HandleFunc(
|
||||||
|
"/blocks",
|
||||||
|
makeHandler(getBlocksHandler)).
|
||||||
|
Methods("GET")
|
||||||
|
|
||||||
router.HandleFunc(
|
router.HandleFunc(
|
||||||
"/fee-estimates",
|
"/fee-estimates",
|
||||||
makeHandler(getFeeEstimatesHandler)).
|
makeHandler(getFeeEstimatesHandler)).
|
||||||
Methods("GET")
|
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])
|
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])
|
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string][]string) (interface{}, *utils.HandlerError) {
|
func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
|
||||||
skip := 0
|
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||||
limit := defaultGetTransactionsLimit
|
if hErr != nil {
|
||||||
if len(queryParams[queryParamSkip]) > 1 {
|
return nil, hErr
|
||||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+
|
|
||||||
" expected a single value but got an array", queryParamSkip))
|
|
||||||
}
|
}
|
||||||
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
|
var err error
|
||||||
skip, err = strconv.Atoi(queryParams[queryParamSkip][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", 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])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
|
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))
|
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])
|
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])
|
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()
|
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))
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user