[NOD-398] Change API server type HandlerError to work with errors instead of error strings (#454)

* [NOD-398] Change API server type HandlerError to work with errors instead of error strings

* [NOD-398] Rename OriginalError -> Cause and isHandleError -> ok
This commit is contained in:
Ori Newman 2019-11-06 16:58:58 +02:00 committed by Svarog
parent 3cc6f2d648
commit a9ff9b0e70
8 changed files with 120 additions and 113 deletions

View File

@ -2,10 +2,10 @@ package controllers
import ( import (
"encoding/hex" "encoding/hex"
"fmt"
"github.com/daglabs/btcd/apiserver/apimodels" "github.com/daglabs/btcd/apiserver/apimodels"
"github.com/daglabs/btcd/apiserver/dbmodels" "github.com/daglabs/btcd/apiserver/dbmodels"
"github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net/http" "net/http"
"github.com/daglabs/btcd/apiserver/database" "github.com/daglabs/btcd/apiserver/database"
@ -27,38 +27,38 @@ const (
const maxGetBlocksLimit = 100 const maxGetBlocksLimit = 100
// GetBlockByHashHandler returns a block by a given hash. // GetBlockByHashHandler returns a block by a given hash.
func GetBlockByHashHandler(blockHash string) (interface{}, *httpserverutils.HandlerError) { func GetBlockByHashHandler(blockHash string) (interface{}, error) {
if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize { if bytes, err := hex.DecodeString(blockHash); err != nil || len(bytes) != daghash.HashSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given block hash is not a hex-encoded %d-byte hash.", daghash.HashSize)) errors.Errorf("The given block hash is not a hex-encoded %d-byte hash.", daghash.HashSize))
} }
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) return nil, err
} }
block := &dbmodels.Block{} block := &dbmodels.Block{}
dbResult := db.Where(&dbmodels.Block{BlockHash: blockHash}).Preload("AcceptingBlock").First(block) dbResult := db.Where(&dbmodels.Block{BlockHash: blockHash}).Preload("AcceptingBlock").First(block)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) { if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No block with the given block hash was found.") return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.New("No block with the given block hash was found"))
} }
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbResult.GetErrors()) return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbResult.GetErrors())
} }
return convertBlockModelToBlockResponse(block), nil return convertBlockModelToBlockResponse(block), nil
} }
// GetBlocksHandler searches for all blocks // GetBlocksHandler searches for all blocks
func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *httpserverutils.HandlerError) { func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, error) {
if limit > maxGetBlocksLimit { if limit > maxGetBlocksLimit {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit)) return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
} }
blocks := []*dbmodels.Block{} blocks := []*dbmodels.Block{}
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) return nil, err
} }
query := db. query := db.
Limit(limit). Limit(limit).
@ -69,7 +69,7 @@ func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *ht
} else if order == OrderDescending { } else if order == OrderDescending {
query = query.Order("`id` DESC") query = query.Order("`id` DESC")
} else { } else {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid order", order)) return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("'%s' is not a valid order", order))
} }
query.Find(&blocks) query.Find(&blocks)
blockResponses := make([]*apimodels.BlockResponse, len(blocks)) blockResponses := make([]*apimodels.BlockResponse, len(blocks))

View File

@ -10,6 +10,7 @@ import (
"github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/blockdag"
"github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/httpserverutils"
"github.com/daglabs/btcd/util/subnetworkid" "github.com/daglabs/btcd/util/subnetworkid"
"github.com/pkg/errors"
"net/http" "net/http"
"github.com/daglabs/btcd/apiserver/database" "github.com/daglabs/btcd/apiserver/database"
@ -23,15 +24,15 @@ import (
const maxGetTransactionsLimit = 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{}, *httpserverutils.HandlerError) { func GetTransactionByIDHandler(txID string) (interface{}, error) {
if bytes, err := hex.DecodeString(txID); err != nil || len(bytes) != daghash.TxIDSize { if bytes, err := hex.DecodeString(txID); err != nil || len(bytes) != daghash.TxIDSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize)) errors.Errorf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
} }
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) return nil, err
} }
tx := &dbmodels.Transaction{} tx := &dbmodels.Transaction{}
@ -39,24 +40,24 @@ func GetTransactionByIDHandler(txID string) (interface{}, *httpserverutils.Handl
dbResult := addTxPreloadedFields(query).First(&tx) dbResult := addTxPreloadedFields(query).First(&tx)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) { if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No transaction with the given txid was found.") return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.New("No transaction with the given txid was found"))
} }
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors) return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
} }
return convertTxDBModelToTxResponse(tx), nil return convertTxDBModelToTxResponse(tx), nil
} }
// GetTransactionByHashHandler returns a transaction by a given transaction hash. // GetTransactionByHashHandler returns a transaction by a given transaction hash.
func GetTransactionByHashHandler(txHash string) (interface{}, *httpserverutils.HandlerError) { func GetTransactionByHashHandler(txHash string) (interface{}, error) {
if bytes, err := hex.DecodeString(txHash); err != nil || len(bytes) != daghash.HashSize { if bytes, err := hex.DecodeString(txHash); err != nil || len(bytes) != daghash.HashSize {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize)) errors.Errorf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
} }
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) return nil, err
} }
tx := &dbmodels.Transaction{} tx := &dbmodels.Transaction{}
@ -64,25 +65,25 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *httpserverutils.H
dbResult := addTxPreloadedFields(query).First(&tx) dbResult := addTxPreloadedFields(query).First(&tx)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.IsDBRecordNotFoundError(dbErrors) { if httpserverutils.IsDBRecordNotFoundError(dbErrors) {
return nil, httpserverutils.NewHandlerError(http.StatusNotFound, "No transaction with the given txhash was found.") return nil, httpserverutils.NewHandlerError(http.StatusNotFound, errors.Errorf("No transaction with the given txhash was found."))
} }
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors) return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transaction from the database:", dbErrors)
} }
return convertTxDBModelToTxResponse(tx), nil return convertTxDBModelToTxResponse(tx), nil
} }
// 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{}, *httpserverutils.HandlerError) { func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, error) {
if limit > maxGetTransactionsLimit { if limit > maxGetTransactionsLimit {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit)) errors.Errorf("The maximum allowed value for the limit is %d", maxGetTransactionsLimit))
} }
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) return nil, err
} }
txs := []*dbmodels.Transaction{} txs := []*dbmodels.Transaction{}
@ -90,7 +91,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
Joins("LEFT JOIN `transaction_outputs` ON `transaction_outputs`.`transaction_id` = `transactions`.`id`"). Joins("LEFT JOIN `transaction_outputs` ON `transaction_outputs`.`transaction_id` = `transactions`.`id`").
Joins("LEFT JOIN `addresses` AS `out_addresses` ON `out_addresses`.`id` = `transaction_outputs`.`address_id`"). Joins("LEFT JOIN `addresses` AS `out_addresses` ON `out_addresses`.`id` = `transaction_outputs`.`address_id`").
Joins("LEFT JOIN `transaction_inputs` ON `transaction_inputs`.`transaction_id` = `transactions`.`id`"). Joins("LEFT JOIN `transaction_inputs` ON `transaction_inputs`.`transaction_id` = `transactions`.`id`").
Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`transaction_output_id`"). Joins("LEFT JOIN `transaction_outputs` AS `inputs_outs` ON `inputs_outs`.`id` = `transaction_inputs`.`previous_transaction_output_id`").
Joins("LEFT JOIN `addresses` AS `in_addresses` ON `in_addresses`.`id` = `inputs_outs`.`address_id`"). Joins("LEFT JOIN `addresses` AS `in_addresses` ON `in_addresses`.`id` = `inputs_outs`.`address_id`").
Where("`out_addresses`.`address` = ?", address). Where("`out_addresses`.`address` = ?", address).
Or("`in_addresses`.`address` = ?", address). Or("`in_addresses`.`address` = ?", address).
@ -100,7 +101,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
dbResult := addTxPreloadedFields(query).Find(&txs) dbResult := addTxPreloadedFields(query).Find(&txs)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors) return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
} }
txResponses := make([]*apimodels.TransactionResponse, len(txs)) txResponses := make([]*apimodels.TransactionResponse, len(txs))
for i, tx := range txs { for i, tx := range txs {
@ -109,10 +110,10 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
return txResponses, nil return txResponses, nil
} }
func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) { func fetchSelectedTipBlueScore() (uint64, error) {
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return 0, httpserverutils.NewInternalServerHandlerError(err.Error()) return 0, err
} }
block := &dbmodels.Block{} block := &dbmodels.Block{}
dbResult := db.Order("blue_score DESC"). dbResult := db.Order("blue_score DESC").
@ -121,16 +122,16 @@ func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) {
First(block) First(block)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return 0, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors) return 0, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading transactions from the database:", dbErrors)
} }
return block.BlueScore, nil return block.BlueScore, nil
} }
// GetUTXOsByAddressHandler searches for all UTXOs that belong to a certain address. // GetUTXOsByAddressHandler searches for all UTXOs that belong to a certain address.
func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.HandlerError) { func GetUTXOsByAddressHandler(address string) (interface{}, error) {
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) return nil, err
} }
var transactionOutputs []*dbmodels.TransactionOutput var transactionOutputs []*dbmodels.TransactionOutput
@ -141,12 +142,12 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han
Preload("Transaction.Subnetwork"). Preload("Transaction.Subnetwork").
Find(&transactionOutputs).GetErrors() Find(&transactionOutputs).GetErrors()
if len(dbErrors) > 0 { if len(dbErrors) > 0 {
return nil, httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when loading UTXOs from the database:", dbErrors) return nil, httpserverutils.NewErrorFromDBErrors("Some errors were encountered when loading UTXOs from the database:", dbErrors)
} }
selectedTipBlueScore, hErr := fetchSelectedTipBlueScore() selectedTipBlueScore, err := fetchSelectedTipBlueScore()
if hErr != nil { if err != nil {
return nil, hErr return nil, err
} }
UTXOsResponses := make([]*apimodels.TransactionOutputResponse, len(transactionOutputs)) UTXOsResponses := make([]*apimodels.TransactionOutputResponse, len(transactionOutputs))
@ -154,7 +155,7 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han
subnetworkID := &subnetworkid.SubnetworkID{} subnetworkID := &subnetworkid.SubnetworkID{}
err := subnetworkid.Decode(subnetworkID, transactionOutput.Transaction.Subnetwork.SubnetworkID) err := subnetworkid.Decode(subnetworkID, transactionOutput.Transaction.Subnetwork.SubnetworkID)
if err != nil { if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(fmt.Sprintf("Couldn't decode subnetwork id %s: %s", transactionOutput.Transaction.Subnetwork.SubnetworkID, err)) return nil, errors.Wrap(err, fmt.Sprintf("Couldn't decode subnetwork id %s", transactionOutput.Transaction.Subnetwork.SubnetworkID))
} }
var acceptingBlockHash *string var acceptingBlockHash *string
var confirmations uint64 var confirmations uint64
@ -188,24 +189,24 @@ func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
} }
// PostTransaction forwards a raw transaction to the JSON-RPC API server // PostTransaction forwards a raw transaction to the JSON-RPC API server
func PostTransaction(requestBody []byte) *httpserverutils.HandlerError { func PostTransaction(requestBody []byte) error {
client, err := jsonrpc.GetClient() client, err := jsonrpc.GetClient()
if err != nil { if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error()) return err
} }
rawTx := &apimodels.RawTransaction{} rawTx := &apimodels.RawTransaction{}
err = json.Unmarshal(requestBody, rawTx) err = json.Unmarshal(requestBody, rawTx)
if err != nil { if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error unmarshalling request body: %s", err), errors.Wrap(err, "Error unmarshalling request body"),
"The request body is not json-formatted") "The request body is not json-formatted")
} }
txBytes, err := hex.DecodeString(rawTx.RawTransaction) txBytes, err := hex.DecodeString(rawTx.RawTransaction)
if err != nil { if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding hex raw transaction: %s", err), errors.Wrap(err, "Error decoding hex raw transaction"),
"The raw transaction is not a hex-encoded transaction") "The raw transaction is not a hex-encoded transaction")
} }
@ -214,16 +215,16 @@ func PostTransaction(requestBody []byte) *httpserverutils.HandlerError {
err = tx.BtcDecode(txReader, 0) err = tx.BtcDecode(txReader, 0)
if err != nil { if err != nil {
return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding raw transaction: %s", err), errors.Wrap(err, "Error decoding raw transaction"),
"Error decoding raw transaction") "Error decoding raw transaction")
} }
_, err = client.SendRawTransaction(tx, true) _, err = client.SendRawTransaction(tx, true)
if err != nil { if err != nil {
if rpcErr, ok := err.(*btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify { if rpcErr, ok := err.(*btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify {
return httpserverutils.NewHandlerError(http.StatusInternalServerError, rpcErr.Message) return httpserverutils.NewHandlerError(http.StatusInternalServerError, err)
} }
return httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) return err
} }
return nil return nil

View File

@ -3,6 +3,7 @@ package server
import ( import (
"fmt" "fmt"
"github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net/http" "net/http"
"strconv" "strconv"
@ -29,7 +30,7 @@ const (
defaultGetBlocksOrder = controllers.OrderAscending defaultGetBlocksOrder = controllers.OrderAscending
) )
func mainHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, _ []byte) (interface{}, *httpserverutils.HandlerError) { func mainHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, _ []byte) (interface{}, error) {
return struct { return struct {
Message string `json:"message"` Message string `json:"message"`
}{ }{
@ -81,11 +82,11 @@ func addRoutes(router *mux.Router) {
Methods("POST") Methods("POST")
} }
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *httpserverutils.HandlerError) { func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, error) {
if _, ok := queryParams[param]; ok { if _, ok := queryParams[param]; ok {
intValue, err := strconv.Atoi(queryParams[param]) intValue, err := strconv.Atoi(queryParams[param])
if err != nil { if err != nil {
return 0, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", param, err)) return 0, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Wrap(err, fmt.Sprintf("Couldn't parse the '%s' query parameter", param)))
} }
return intValue, nil return intValue, nil
} }
@ -93,72 +94,72 @@ func convertQueryParamToInt(queryParams map[string]string, param string, default
} }
func getTransactionByIDHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string, func getTransactionByIDHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID]) return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID])
} }
func getTransactionByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string, func getTransactionByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash]) return controllers.GetTransactionByHashHandler(routeParams[routeParamTxHash])
} }
func getTransactionsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, queryParams map[string]string, func getTransactionsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, queryParams map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0) skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil { if err != nil {
return nil, hErr return nil, err
} }
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit) limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit)
if hErr != nil { if err != nil {
return nil, hErr return nil, err
} }
if _, ok := queryParams[queryParamLimit]; ok { if _, ok := queryParams[queryParamLimit]; ok {
var err error var err error
skip, err = strconv.Atoi(queryParams[queryParamLimit]) skip, err = strconv.Atoi(queryParams[queryParamLimit])
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity,
fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err)) errors.Wrap(err, fmt.Sprintf("Couldn't parse the '%s' query parameter", queryParamLimit)))
} }
} }
return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit)) return controllers.GetTransactionsByAddressHandler(routeParams[routeParamAddress], uint64(skip), uint64(limit))
} }
func getUTXOsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string, func getUTXOsByAddressHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress]) return controllers.GetUTXOsByAddressHandler(routeParams[routeParamAddress])
} }
func getBlockByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string, func getBlockByHashHandler(_ *httpserverutils.ServerContext, _ *http.Request, routeParams map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash]) return controllers.GetBlockByHashHandler(routeParams[routeParamBlockHash])
} }
func getFeeEstimatesHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, func getFeeEstimatesHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
return controllers.GetFeeEstimatesHandler() return controllers.GetFeeEstimatesHandler()
} }
func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, queryParams map[string]string, func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, queryParams map[string]string,
_ []byte) (interface{}, *httpserverutils.HandlerError) { _ []byte) (interface{}, error) {
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0) skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0)
if hErr != nil { if err != nil {
return nil, hErr return nil, err
} }
limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit) limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit)
if hErr != nil { if err != nil {
return nil, hErr return nil, err
} }
order := defaultGetBlocksOrder order := defaultGetBlocksOrder
if orderParamValue, ok := queryParams[queryParamOrder]; ok { if orderParamValue, ok := queryParams[queryParamOrder]; ok {
if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending { if orderParamValue != controllers.OrderAscending && orderParamValue != controllers.OrderDescending {
return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit)) return nil, httpserverutils.NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("'%s' is not a valid value for the '%s' query parameter", orderParamValue, queryParamLimit))
} }
order = orderParamValue order = orderParamValue
} }
@ -166,6 +167,6 @@ func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[s
} }
func postTransactionHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, func postTransactionHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string,
requestBody []byte) (interface{}, *httpserverutils.HandlerError) { requestBody []byte) (interface{}, error) {
return nil, controllers.PostTransaction(requestBody) return nil, controllers.PostTransaction(requestBody)
} }

View File

@ -3,6 +3,7 @@ package main
import ( import (
"github.com/daglabs/btcd/faucet/database" "github.com/daglabs/btcd/faucet/database"
"github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/httpserverutils"
"github.com/pkg/errors"
"net" "net"
"net/http" "net/http"
"time" "time"
@ -23,43 +24,43 @@ func ipFromRequest(r *http.Request) (string, error) {
return ip, nil return ip, nil
} }
func validateIPUsage(r *http.Request) *httpserverutils.HandlerError { func validateIPUsage(r *http.Request) error {
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error()) return err
} }
now := time.Now() now := time.Now()
timeBeforeMinRequestInterval := now.Add(-minRequestInterval) timeBeforeMinRequestInterval := now.Add(-minRequestInterval)
var count int var count int
ip, err := ipFromRequest(r) ip, err := ipFromRequest(r)
if err != nil { if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error()) return err
} }
dbResult := db.Model(&ipUse{}).Where(&ipUse{IP: ip}).Where("last_use BETWEEN ? AND ?", timeBeforeMinRequestInterval, now).Count(&count) dbResult := db.Model(&ipUse{}).Where(&ipUse{IP: ip}).Where("last_use BETWEEN ? AND ?", timeBeforeMinRequestInterval, now).Count(&count)
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when checking the last use of an IP:", dbResult.GetErrors()) return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when checking the last use of an IP:", dbResult.GetErrors())
} }
if count != 0 { if count != 0 {
return httpserverutils.NewHandlerError(http.StatusForbidden, "A user is allowed to to have one request from the faucet every 24 hours.") return httpserverutils.NewHandlerError(http.StatusForbidden, errors.New("A user is allowed to to have one request from the faucet every 24 hours"))
} }
return nil return nil
} }
func updateIPUsage(r *http.Request) *httpserverutils.HandlerError { func updateIPUsage(r *http.Request) error {
db, err := database.DB() db, err := database.DB()
if err != nil { if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error()) return err
} }
ip, err := ipFromRequest(r) ip, err := ipFromRequest(r)
if err != nil { if err != nil {
return httpserverutils.NewInternalServerHandlerError(err.Error()) return err
} }
dbResult := db.Where(&ipUse{IP: ip}).Assign(&ipUse{LastUse: time.Now()}).FirstOrCreate(&ipUse{}) dbResult := db.Where(&ipUse{IP: ip}).Assign(&ipUse{LastUse: time.Now()}).FirstOrCreate(&ipUse{})
dbErrors := dbResult.GetErrors() dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) { if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewHandlerErrorFromDBErrors("Some errors were encountered when upserting the IP to the new date:", dbResult.GetErrors()) return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when upserting the IP to the new date:", dbResult.GetErrors())
} }
return nil return nil
} }

View File

@ -3,10 +3,10 @@ package main
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"github.com/daglabs/btcd/faucet/config" "github.com/daglabs/btcd/faucet/config"
"github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/httpserverutils"
"github.com/daglabs/btcd/util" "github.com/daglabs/btcd/util"
"github.com/pkg/errors"
"net/http" "net/http"
"time" "time"
@ -51,7 +51,7 @@ type requestMoneyData struct {
} }
func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ map[string]string, _ map[string]string, func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ map[string]string, _ map[string]string,
requestBody []byte) (interface{}, *httpserverutils.HandlerError) { requestBody []byte) (interface{}, error) {
hErr := validateIPUsage(r) hErr := validateIPUsage(r)
if hErr != nil { if hErr != nil {
return nil, hErr return nil, hErr
@ -60,18 +60,18 @@ func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ ma
err := json.Unmarshal(requestBody, requestData) err := json.Unmarshal(requestBody, requestData)
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error unmarshalling request body: %s", err), errors.Wrap(err, "Error unmarshalling request body"),
"The request body is not json-formatted") "The request body is not json-formatted")
} }
address, err := util.DecodeAddress(requestData.Address, config.ActiveNetParams().Prefix) address, err := util.DecodeAddress(requestData.Address, config.ActiveNetParams().Prefix)
if err != nil { if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
fmt.Sprintf("Error decoding address: %s", err), errors.Wrap(err, "Error decoding address"),
"Error decoding address") "Error decoding address")
} }
tx, err := sendToAddress(address) tx, err := sendToAddress(address)
if err != nil { if err != nil {
return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) return nil, err
} }
hErr = updateIPUsage(r) hErr = updateIPUsage(r)
if hErr != nil { if hErr != nil {

View File

@ -13,29 +13,29 @@ import (
// a rest route handler or a middleware. // a rest route handler or a middleware.
type HandlerError struct { type HandlerError struct {
Code int Code int
Message string Cause error
ClientMessage string ClientMessage string
} }
func (hErr *HandlerError) Error() string { func (hErr *HandlerError) Error() string {
return hErr.Message return hErr.Cause.Error()
} }
// NewHandlerError returns a HandlerError with the given code and message. // NewHandlerError returns a HandlerError with the given code and message.
func NewHandlerError(code int, message string) *HandlerError { func NewHandlerError(code int, err error) error {
return &HandlerError{ return &HandlerError{
Code: code, Code: code,
Message: message, Cause: err,
ClientMessage: message, ClientMessage: err.Error(),
} }
} }
// NewHandlerErrorWithCustomClientMessage returns a HandlerError with // NewHandlerErrorWithCustomClientMessage returns a HandlerError with
// the given code, message and client error message. // the given code, message and client error message.
func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage string) *HandlerError { func NewHandlerErrorWithCustomClientMessage(code int, err error, clientMessage string) error {
return &HandlerError{ return &HandlerError{
Code: code, Code: code,
Message: message, Cause: err,
ClientMessage: clientMessage, ClientMessage: clientMessage,
} }
} }
@ -43,8 +43,8 @@ func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage str
// NewInternalServerHandlerError returns a HandlerError with // NewInternalServerHandlerError returns a HandlerError with
// the given message, and the http.StatusInternalServerError // the given message, and the http.StatusInternalServerError
// status text as client message. // status text as client message.
func NewInternalServerHandlerError(message string) *HandlerError { func NewInternalServerHandlerError(err error) error {
return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, message, http.StatusText(http.StatusInternalServerError)) return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, err, http.StatusText(http.StatusInternalServerError))
} }
// NewErrorFromDBErrors takes a slice of database errors and a prefix, and // NewErrorFromDBErrors takes a slice of database errors and a prefix, and
@ -58,13 +58,6 @@ func NewErrorFromDBErrors(prefix string, dbErrors []error) error {
return errors.Errorf("%s [%s]", prefix, strings.Join(dbErrorsStrings, ",")) return errors.Errorf("%s [%s]", prefix, strings.Join(dbErrorsStrings, ","))
} }
// NewHandlerErrorFromDBErrors takes a slice of database errors and a prefix, and
// returns an HandlerError with error code http.StatusInternalServerError with
// all of the database errors formatted to one string with the given prefix
func NewHandlerErrorFromDBErrors(prefix string, dbErrors []error) *HandlerError {
return NewInternalServerHandlerError(NewErrorFromDBErrors(prefix, dbErrors).Error())
}
// IsDBRecordNotFoundError returns true if the given dbErrors contains only a RecordNotFound error // IsDBRecordNotFoundError returns true if the given dbErrors contains only a RecordNotFound error
func IsDBRecordNotFoundError(dbErrors []error) bool { func IsDBRecordNotFoundError(dbErrors []error) bool {
return len(dbErrors) == 1 && gorm.IsRecordNotFoundError(dbErrors[0]) return len(dbErrors) == 1 && gorm.IsRecordNotFoundError(dbErrors[0])
@ -88,9 +81,13 @@ func (err *ClientError) Error() string {
// SendErr takes a HandlerError and create a ClientError out of it that is sent // SendErr takes a HandlerError and create a ClientError out of it that is sent
// to the http client. // to the http client.
func SendErr(ctx *ServerContext, w http.ResponseWriter, hErr *HandlerError) { func SendErr(ctx *ServerContext, w http.ResponseWriter, err error) {
errMsg := fmt.Sprintf("got error: %s", hErr) var hErr *HandlerError
ctx.Warnf(errMsg) var ok bool
if hErr, ok = err.(*HandlerError); !ok {
hErr = NewInternalServerHandlerError(err).(*HandlerError)
}
ctx.Warnf("got error: %s", err)
w.WriteHeader(hErr.Code) w.WriteHeader(hErr.Code)
SendJSONResponse(w, &ClientError{ SendJSONResponse(w, &ClientError{
ErrorCode: hErr.Code, ErrorCode: hErr.Code,

View File

@ -2,6 +2,7 @@ package httpserverutils
import ( import (
"fmt" "fmt"
"github.com/pkg/errors"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
) )
@ -38,10 +39,16 @@ func RecoveryMiddleware(h http.Handler) http.Handler {
defer func() { defer func() {
recoveryErr := recover() recoveryErr := recover()
if recoveryErr != nil { if recoveryErr != nil {
var recoveryErrAsError error
if rErr, ok := recoveryErr.(error); ok {
recoveryErrAsError = rErr
} else {
recoveryErrAsError = errors.Errorf("%s", recoveryErr)
}
recoveryErrStr := fmt.Sprintf("%s", recoveryErr) recoveryErrStr := fmt.Sprintf("%s", recoveryErr)
log.Criticalf("Fatal error: %s", recoveryErrStr) log.Criticalf("Fatal error: %+v", recoveryErrStr)
log.Criticalf("Stack trace: %s", debug.Stack()) log.Criticalf("Stack trace: %s", debug.Stack())
SendErr(ctx, w, NewInternalServerHandlerError(recoveryErrStr)) SendErr(ctx, w, recoveryErrAsError)
} }
}() }()
h.ServeHTTP(w, r) h.ServeHTTP(w, r)

View File

@ -1,8 +1,8 @@
package httpserverutils package httpserverutils
import ( import (
"fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/pkg/errors"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
) )
@ -11,7 +11,7 @@ import (
// MakeHandler wrapper and gets the relevant request fields // MakeHandler wrapper and gets the relevant request fields
// from it. // from it.
type HandlerFunc func(ctx *ServerContext, r *http.Request, routeParams map[string]string, queryParams map[string]string, requestBody []byte) ( type HandlerFunc func(ctx *ServerContext, r *http.Request, routeParams map[string]string, queryParams map[string]string, requestBody []byte) (
interface{}, *HandlerError) interface{}, error)
// MakeHandler is a wrapper function that takes a handler in the form of HandlerFunc // MakeHandler is a wrapper function that takes a handler in the form of HandlerFunc
// and returns a function that can be used as a handler in mux.Router.HandleFunc. // and returns a function that can be used as a handler in mux.Router.HandleFunc.
@ -24,19 +24,19 @@ func MakeHandler(handler HandlerFunc) func(http.ResponseWriter, *http.Request) {
var err error var err error
requestBody, err = ioutil.ReadAll(r.Body) requestBody, err = ioutil.ReadAll(r.Body)
if err != nil { if err != nil {
SendErr(ctx, w, NewInternalServerHandlerError("Error reading POST data")) SendErr(ctx, w, errors.New("Error reading POST data"))
} }
} }
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query()) flattenedQueryParams, err := flattenQueryParams(r.URL.Query())
if hErr != nil { if err != nil {
SendErr(ctx, w, hErr) SendErr(ctx, w, err)
return return
} }
response, hErr := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody) response, err := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody)
if hErr != nil { if err != nil {
SendErr(ctx, w, hErr) SendErr(ctx, w, err)
return return
} }
if response != nil { if response != nil {
@ -45,11 +45,11 @@ func MakeHandler(handler HandlerFunc) func(http.ResponseWriter, *http.Request) {
} }
} }
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *HandlerError) { func flattenQueryParams(queryParams map[string][]string) (map[string]string, error) {
flattenedMap := make(map[string]string) flattenedMap := make(map[string]string)
for param, valuesSlice := range queryParams { for param, valuesSlice := range queryParams {
if len(valuesSlice) > 1 { if len(valuesSlice) > 1 {
return nil, NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("Couldn't parse the '%s' query parameter:"+ return nil, NewHandlerError(http.StatusUnprocessableEntity, errors.Errorf("Couldn't parse the '%s' query parameter:"+
" expected a single value but got multiple values", param)) " expected a single value but got multiple values", param))
} }
flattenedMap[param] = valuesSlice[0] flattenedMap[param] = valuesSlice[0]