mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-06 22:26:47 +00:00
[NOD-309] post transaction (#403)
* [NOD-319] Add query params to api server route handler * Temp commit * [NOD-322] Make database.DB a function * [NOD-322] Move context to be the first parameter in all functions * [NOD-322] Set db to nil on database.Close() * [NOD-322] Tidy go.mod/go.sum * [NOD-323] Move rpc-client to separate package * [NOD-309] Add controller for POST /transaction * [NOD-309] Added route for POST /transaction * [NOD-309] in POST /transaction: Forward reject errors to client * [NOD-309] Added custom client messages to errors in POST /transaction * [NOD-309] Use utils.NewInternalServerHandlerError where appropriate
This commit is contained in:
parent
47c5eddf38
commit
d4083cbdbe
6
apiserver/controllers/request_types.go
Normal file
6
apiserver/controllers/request_types.go
Normal file
@ -0,0 +1,6 @@
|
||||
package controllers
|
||||
|
||||
// RawTransaction represents a raw transaction posted to the API server
|
||||
type RawTransaction struct {
|
||||
RawTransaction string `json:"rawTransaction"`
|
||||
}
|
@ -1,14 +1,19 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/database"
|
||||
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
||||
"github.com/daglabs/btcd/apiserver/models"
|
||||
"github.com/daglabs/btcd/apiserver/utils"
|
||||
"github.com/daglabs/btcd/btcjson"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
"github.com/daglabs/btcd/wire"
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
||||
@ -47,7 +52,7 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerErro
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
tx := &models.Transaction{}
|
||||
@ -72,7 +77,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
|
||||
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
txs := []*models.Transaction{}
|
||||
@ -102,7 +107,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
|
||||
func GetUTXOsByAddressHandler(address string) (interface{}, *utils.HandlerError) {
|
||||
db, err := database.DB()
|
||||
if err != nil {
|
||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
||||
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
var transactionOutputs []*models.TransactionOutput
|
||||
@ -135,3 +140,45 @@ func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
|
||||
Preload("TransactionInputs.TransactionOutput.Transaction").
|
||||
Preload("TransactionInputs.TransactionOutput.Address")
|
||||
}
|
||||
|
||||
// PostTransaction forwards a raw transaction to the JSON-RPC API server
|
||||
func PostTransaction(requestBody []byte) *utils.HandlerError {
|
||||
client, err := jsonrpc.GetClient()
|
||||
if err != nil {
|
||||
return utils.NewInternalServerHandlerError(err.Error())
|
||||
}
|
||||
|
||||
rawTx := &RawTransaction{}
|
||||
err = json.Unmarshal(requestBody, rawTx)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error unmarshalling request body: %s", err),
|
||||
"The request body is not json-formatted")
|
||||
}
|
||||
|
||||
txBytes, err := hex.DecodeString(rawTx.RawTransaction)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error decoding hex raw transaction: %s", err),
|
||||
"The raw transaction is not a hex-encoded transaction")
|
||||
}
|
||||
|
||||
txReader := bytes.NewReader(txBytes)
|
||||
tx := &wire.MsgTx{}
|
||||
err = tx.BtcDecode(txReader, 0)
|
||||
if err != nil {
|
||||
return utils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Error decoding raw transaction: %s", err),
|
||||
"Error decoding raw transaction")
|
||||
}
|
||||
|
||||
_, err = client.SendRawTransaction(tx, true)
|
||||
if err != nil {
|
||||
if rpcErr, ok := err.(btcjson.RPCError); ok && rpcErr.Code == btcjson.ErrRPCVerify {
|
||||
return utils.NewHandlerError(http.StatusInternalServerError, rpcErr.Message)
|
||||
}
|
||||
return utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ func Connect(cfg *config.Config) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
db.SetLogger(gormLogger{})
|
||||
return nil
|
||||
}
|
||||
|
@ -3,9 +3,11 @@ package jsonrpc
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/daglabs/btcd/apiserver/config"
|
||||
"github.com/daglabs/btcd/util/daghash"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/daglabs/btcd/rpcclient"
|
||||
"github.com/daglabs/btcd/util"
|
||||
@ -62,11 +64,12 @@ func Connect(cfg *config.Config) error {
|
||||
}
|
||||
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
Host: cfg.RPCServer,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
DisableTLS: cfg.DisableTLS,
|
||||
Host: cfg.RPCServer,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
Pass: cfg.RPCPassword,
|
||||
DisableTLS: cfg.DisableTLS,
|
||||
RequestTimeout: time.Second * 5,
|
||||
}
|
||||
|
||||
if !cfg.DisableTLS {
|
||||
|
@ -3,6 +3,7 @@ package server
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@ -30,23 +31,36 @@ const (
|
||||
defaultGetBlocksOrder = controllers.OrderAscending
|
||||
)
|
||||
|
||||
func makeHandler(
|
||||
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) {
|
||||
type handlerFunc func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string, requestBody []byte) (
|
||||
interface{}, *utils.HandlerError)
|
||||
|
||||
func makeHandler(handler handlerFunc) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := utils.ToAPIServerContext(r.Context())
|
||||
|
||||
var requestBody []byte
|
||||
if r.Method == "POST" {
|
||||
var err error
|
||||
requestBody, err = ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
sendErr(ctx, w, utils.NewHandlerError(500, "Internal server error occured"))
|
||||
}
|
||||
}
|
||||
|
||||
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
|
||||
if hErr != nil {
|
||||
sendErr(ctx, w, hErr)
|
||||
return
|
||||
}
|
||||
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams)
|
||||
|
||||
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams, requestBody)
|
||||
if hErr != nil {
|
||||
sendErr(ctx, w, hErr)
|
||||
return
|
||||
}
|
||||
sendJSONResponse(w, response)
|
||||
if response != nil {
|
||||
sendJSONResponse(w, response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,7 +102,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, routeParams map[string]string, _ map[string]string, _ []byte) (interface{}, *utils.HandlerError) {
|
||||
return struct {
|
||||
Message string `json:"message"`
|
||||
}{
|
||||
@ -133,6 +147,11 @@ func addRoutes(router *mux.Router) {
|
||||
"/fee-estimates",
|
||||
makeHandler(getFeeEstimatesHandler)).
|
||||
Methods("GET")
|
||||
|
||||
router.HandleFunc(
|
||||
"/transaction",
|
||||
makeHandler(postTransactionHandler)).
|
||||
Methods("POST")
|
||||
}
|
||||
|
||||
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) {
|
||||
@ -146,15 +165,21 @@ func convertQueryParamToInt(queryParams map[string]string, param string, default
|
||||
return defaultValue, nil
|
||||
}
|
||||
|
||||
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string) (interface{}, *utils.HandlerError) {
|
||||
func getTransactionByIDHandler(_ *utils.APIServerContext, routeParams map[string]string, _ map[string]string,
|
||||
_ []byte) (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,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
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,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
@ -167,25 +192,34 @@ func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[
|
||||
var err error
|
||||
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))
|
||||
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity,
|
||||
fmt.Sprintf("Couldn't parse the '%s' query parameter: %s", queryParamLimit, err))
|
||||
}
|
||||
}
|
||||
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,
|
||||
_ []byte) (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,
|
||||
_ []byte) (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,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
return controllers.GetFeeEstimatesHandler()
|
||||
}
|
||||
|
||||
func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string) (interface{}, *utils.HandlerError) {
|
||||
func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParams map[string]string,
|
||||
_ []byte) (interface{}, *utils.HandlerError) {
|
||||
|
||||
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||
if hErr != nil {
|
||||
return nil, hErr
|
||||
@ -203,3 +237,8 @@ func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParam
|
||||
}
|
||||
return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit))
|
||||
}
|
||||
|
||||
func postTransactionHandler(_ *utils.APIServerContext, _ map[string]string, _ map[string]string,
|
||||
requestBody []byte) (interface{}, *utils.HandlerError) {
|
||||
return nil, controllers.PostTransaction(requestBody)
|
||||
}
|
||||
|
@ -2,10 +2,11 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
const gracefulShutdownTimeout = 30 * time.Second
|
||||
@ -26,6 +27,7 @@ func Start(listenAddr string) func() {
|
||||
spawn(func() {
|
||||
log.Errorf("%s", httpServer.ListenAndServe())
|
||||
})
|
||||
|
||||
return func() {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
|
||||
defer cancel()
|
||||
|
@ -3418,7 +3418,7 @@ func handleSendRawTransaction(s *Server, cmd interface{}, closeChan <-chan struc
|
||||
tx.ID(), err)
|
||||
}
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCDeserialization,
|
||||
Code: btcjson.ErrRPCVerify,
|
||||
Message: "TX rejected: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user