diff --git a/apiserver/controllers/block.go b/apiserver/controllers/block.go index 6f3157a42..90c413d29 100644 --- a/apiserver/controllers/block.go +++ b/apiserver/controllers/block.go @@ -2,10 +2,10 @@ package controllers import ( "encoding/hex" - "fmt" "github.com/daglabs/btcd/apiserver/apimodels" "github.com/daglabs/btcd/apiserver/dbmodels" "github.com/daglabs/btcd/httpserverutils" + "github.com/pkg/errors" "net/http" "github.com/daglabs/btcd/apiserver/database" @@ -27,38 +27,38 @@ const ( const maxGetBlocksLimit = 100 // 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 { 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() if err != nil { - return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) + return nil, err } block := &dbmodels.Block{} dbResult := db.Where(&dbmodels.Block{BlockHash: blockHash}).Preload("AcceptingBlock").First(block) dbErrors := dbResult.GetErrors() 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) { - 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 } // 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 { - 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{} db, err := database.DB() if err != nil { - return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return nil, err } query := db. Limit(limit). @@ -69,7 +69,7 @@ func GetBlocksHandler(order string, skip uint64, limit uint64) (interface{}, *ht } else if order == OrderDescending { query = query.Order("`id` DESC") } 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) blockResponses := make([]*apimodels.BlockResponse, len(blocks)) diff --git a/apiserver/controllers/transaction.go b/apiserver/controllers/transaction.go index e3bdfa7f5..1a7924f29 100644 --- a/apiserver/controllers/transaction.go +++ b/apiserver/controllers/transaction.go @@ -10,6 +10,7 @@ import ( "github.com/daglabs/btcd/blockdag" "github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/util/subnetworkid" + "github.com/pkg/errors" "net/http" "github.com/daglabs/btcd/apiserver/database" @@ -23,15 +24,15 @@ import ( const maxGetTransactionsLimit = 1000 // 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 { 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() if err != nil { - return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) + return nil, err } tx := &dbmodels.Transaction{} @@ -39,24 +40,24 @@ func GetTransactionByIDHandler(txID string) (interface{}, *httpserverutils.Handl dbResult := addTxPreloadedFields(query).First(&tx) dbErrors := dbResult.GetErrors() 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) { - 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 } // 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 { 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() if err != nil { - return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return nil, err } tx := &dbmodels.Transaction{} @@ -64,25 +65,25 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *httpserverutils.H dbResult := addTxPreloadedFields(query).First(&tx) dbErrors := dbResult.GetErrors() 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) { - 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 } // 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{}, *httpserverutils.HandlerError) { +func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) (interface{}, error) { if limit > maxGetTransactionsLimit { 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() if err != nil { - return nil, httpserverutils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return nil, err } 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 `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_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`"). Where("`out_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) dbErrors := dbResult.GetErrors() 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)) for i, tx := range txs { @@ -109,10 +110,10 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64) return txResponses, nil } -func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) { +func fetchSelectedTipBlueScore() (uint64, error) { db, err := database.DB() if err != nil { - return 0, httpserverutils.NewInternalServerHandlerError(err.Error()) + return 0, err } block := &dbmodels.Block{} dbResult := db.Order("blue_score DESC"). @@ -121,16 +122,16 @@ func fetchSelectedTipBlueScore() (uint64, *httpserverutils.HandlerError) { First(block) dbErrors := dbResult.GetErrors() 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 } // 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() if err != nil { - return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) + return nil, err } var transactionOutputs []*dbmodels.TransactionOutput @@ -141,12 +142,12 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han Preload("Transaction.Subnetwork"). Find(&transactionOutputs).GetErrors() 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() - if hErr != nil { - return nil, hErr + selectedTipBlueScore, err := fetchSelectedTipBlueScore() + if err != nil { + return nil, err } UTXOsResponses := make([]*apimodels.TransactionOutputResponse, len(transactionOutputs)) @@ -154,7 +155,7 @@ func GetUTXOsByAddressHandler(address string) (interface{}, *httpserverutils.Han subnetworkID := &subnetworkid.SubnetworkID{} err := subnetworkid.Decode(subnetworkID, transactionOutput.Transaction.Subnetwork.SubnetworkID) 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 confirmations uint64 @@ -188,24 +189,24 @@ func addTxPreloadedFields(query *gorm.DB) *gorm.DB { } // 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() if err != nil { - return httpserverutils.NewInternalServerHandlerError(err.Error()) + return err } rawTx := &apimodels.RawTransaction{} err = json.Unmarshal(requestBody, rawTx) if err != nil { 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") } txBytes, err := hex.DecodeString(rawTx.RawTransaction) if err != nil { 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") } @@ -214,16 +215,16 @@ func PostTransaction(requestBody []byte) *httpserverutils.HandlerError { err = tx.BtcDecode(txReader, 0) if err != nil { return httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, - fmt.Sprintf("Error decoding raw transaction: %s", err), + errors.Wrap(err, "Error decoding raw transaction"), "Error decoding raw transaction") } _, err = client.SendRawTransaction(tx, true) if err != nil { 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 diff --git a/apiserver/server/routes.go b/apiserver/server/routes.go index cb60d3557..36eff7d8d 100644 --- a/apiserver/server/routes.go +++ b/apiserver/server/routes.go @@ -3,6 +3,7 @@ package server import ( "fmt" "github.com/daglabs/btcd/httpserverutils" + "github.com/pkg/errors" "net/http" "strconv" @@ -29,7 +30,7 @@ const ( 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 { Message string `json:"message"` }{ @@ -81,11 +82,11 @@ func addRoutes(router *mux.Router) { 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 { intValue, err := strconv.Atoi(queryParams[param]) 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 } @@ -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, - _ []byte) (interface{}, *httpserverutils.HandlerError) { + _ []byte) (interface{}, error) { return controllers.GetTransactionByIDHandler(routeParams[routeParamTxID]) } 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]) } 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) - if hErr != nil { - return nil, hErr + skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0) + if err != nil { + return nil, err } - limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit) - if hErr != nil { - return nil, hErr + limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetTransactionsLimit) + if err != nil { + return nil, err } if _, ok := queryParams[queryParamLimit]; ok { var err error skip, err = strconv.Atoi(queryParams[queryParamLimit]) if err != nil { 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)) } 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]) } 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]) } func getFeeEstimatesHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[string]string, _ map[string]string, - _ []byte) (interface{}, *httpserverutils.HandlerError) { + _ []byte) (interface{}, error) { return controllers.GetFeeEstimatesHandler() } 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) - if hErr != nil { - return nil, hErr + skip, err := convertQueryParamToInt(queryParams, queryParamSkip, 0) + if err != nil { + return nil, err } - limit, hErr := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit) - if hErr != nil { - return nil, hErr + limit, err := convertQueryParamToInt(queryParams, queryParamLimit, defaultGetBlocksLimit) + if err != nil { + return nil, err } order := defaultGetBlocksOrder if orderParamValue, ok := queryParams[queryParamOrder]; ok { 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 } @@ -166,6 +167,6 @@ func getBlocksHandler(_ *httpserverutils.ServerContext, _ *http.Request, _ map[s } 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) } diff --git a/faucet/ip_usage.go b/faucet/ip_usage.go index 59cae4a94..d9c1339ea 100644 --- a/faucet/ip_usage.go +++ b/faucet/ip_usage.go @@ -3,6 +3,7 @@ package main import ( "github.com/daglabs/btcd/faucet/database" "github.com/daglabs/btcd/httpserverutils" + "github.com/pkg/errors" "net" "net/http" "time" @@ -23,43 +24,43 @@ func ipFromRequest(r *http.Request) (string, error) { return ip, nil } -func validateIPUsage(r *http.Request) *httpserverutils.HandlerError { +func validateIPUsage(r *http.Request) error { db, err := database.DB() if err != nil { - return httpserverutils.NewInternalServerHandlerError(err.Error()) + return err } now := time.Now() timeBeforeMinRequestInterval := now.Add(-minRequestInterval) var count int ip, err := ipFromRequest(r) 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) dbErrors := dbResult.GetErrors() 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 { - 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 } -func updateIPUsage(r *http.Request) *httpserverutils.HandlerError { +func updateIPUsage(r *http.Request) error { db, err := database.DB() if err != nil { - return httpserverutils.NewInternalServerHandlerError(err.Error()) + return err } ip, err := ipFromRequest(r) if err != nil { - return httpserverutils.NewInternalServerHandlerError(err.Error()) + return err } dbResult := db.Where(&ipUse{IP: ip}).Assign(&ipUse{LastUse: time.Now()}).FirstOrCreate(&ipUse{}) dbErrors := dbResult.GetErrors() 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 } diff --git a/faucet/server.go b/faucet/server.go index 4372645bb..bd0b1e353 100644 --- a/faucet/server.go +++ b/faucet/server.go @@ -3,10 +3,10 @@ package main import ( "context" "encoding/json" - "fmt" "github.com/daglabs/btcd/faucet/config" "github.com/daglabs/btcd/httpserverutils" "github.com/daglabs/btcd/util" + "github.com/pkg/errors" "net/http" "time" @@ -51,7 +51,7 @@ type requestMoneyData struct { } 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) if hErr != nil { return nil, hErr @@ -60,18 +60,18 @@ func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ ma err := json.Unmarshal(requestBody, requestData) if err != nil { 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") } address, err := util.DecodeAddress(requestData.Address, config.ActiveNetParams().Prefix) if err != nil { return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity, - fmt.Sprintf("Error decoding address: %s", err), + errors.Wrap(err, "Error decoding address"), "Error decoding address") } tx, err := sendToAddress(address) if err != nil { - return nil, httpserverutils.NewInternalServerHandlerError(err.Error()) + return nil, err } hErr = updateIPUsage(r) if hErr != nil { diff --git a/httpserverutils/error.go b/httpserverutils/error.go index d14b91b1d..fa4631228 100644 --- a/httpserverutils/error.go +++ b/httpserverutils/error.go @@ -13,29 +13,29 @@ import ( // a rest route handler or a middleware. type HandlerError struct { Code int - Message string + Cause error ClientMessage string } func (hErr *HandlerError) Error() string { - return hErr.Message + return hErr.Cause.Error() } // 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{ Code: code, - Message: message, - ClientMessage: message, + Cause: err, + ClientMessage: err.Error(), } } // NewHandlerErrorWithCustomClientMessage returns a HandlerError with // 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{ Code: code, - Message: message, + Cause: err, ClientMessage: clientMessage, } } @@ -43,8 +43,8 @@ func NewHandlerErrorWithCustomClientMessage(code int, message, clientMessage str // NewInternalServerHandlerError returns a HandlerError with // the given message, and the http.StatusInternalServerError // status text as client message. -func NewInternalServerHandlerError(message string) *HandlerError { - return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, message, http.StatusText(http.StatusInternalServerError)) +func NewInternalServerHandlerError(err error) error { + return NewHandlerErrorWithCustomClientMessage(http.StatusInternalServerError, err, http.StatusText(http.StatusInternalServerError)) } // 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, ",")) } -// 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 func IsDBRecordNotFoundError(dbErrors []error) bool { 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 // to the http client. -func SendErr(ctx *ServerContext, w http.ResponseWriter, hErr *HandlerError) { - errMsg := fmt.Sprintf("got error: %s", hErr) - ctx.Warnf(errMsg) +func SendErr(ctx *ServerContext, w http.ResponseWriter, err error) { + var hErr *HandlerError + var ok bool + if hErr, ok = err.(*HandlerError); !ok { + hErr = NewInternalServerHandlerError(err).(*HandlerError) + } + ctx.Warnf("got error: %s", err) w.WriteHeader(hErr.Code) SendJSONResponse(w, &ClientError{ ErrorCode: hErr.Code, diff --git a/httpserverutils/middlewares.go b/httpserverutils/middlewares.go index 87b2974dd..dc4ec1e53 100644 --- a/httpserverutils/middlewares.go +++ b/httpserverutils/middlewares.go @@ -2,6 +2,7 @@ package httpserverutils import ( "fmt" + "github.com/pkg/errors" "net/http" "runtime/debug" ) @@ -38,10 +39,16 @@ func RecoveryMiddleware(h http.Handler) http.Handler { defer func() { recoveryErr := recover() 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) - log.Criticalf("Fatal error: %s", recoveryErrStr) + log.Criticalf("Fatal error: %+v", recoveryErrStr) log.Criticalf("Stack trace: %s", debug.Stack()) - SendErr(ctx, w, NewInternalServerHandlerError(recoveryErrStr)) + SendErr(ctx, w, recoveryErrAsError) } }() h.ServeHTTP(w, r) diff --git a/httpserverutils/request.go b/httpserverutils/request.go index 8a4920fc4..f74bb82d6 100644 --- a/httpserverutils/request.go +++ b/httpserverutils/request.go @@ -1,8 +1,8 @@ package httpserverutils import ( - "fmt" "github.com/gorilla/mux" + "github.com/pkg/errors" "io/ioutil" "net/http" ) @@ -11,7 +11,7 @@ import ( // MakeHandler wrapper and gets the relevant request fields // from it. 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 // 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 requestBody, err = ioutil.ReadAll(r.Body) 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()) - if hErr != nil { - SendErr(ctx, w, hErr) + flattenedQueryParams, err := flattenQueryParams(r.URL.Query()) + if err != nil { + SendErr(ctx, w, err) return } - response, hErr := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody) - if hErr != nil { - SendErr(ctx, w, hErr) + response, err := handler(ctx, r, mux.Vars(r), flattenedQueryParams, requestBody) + if err != nil { + SendErr(ctx, w, err) return } 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) for param, valuesSlice := range queryParams { 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)) } flattenedMap[param] = valuesSlice[0]