mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-09 07:36:43 +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
|
package controllers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/daglabs/btcd/apiserver/database"
|
"github.com/daglabs/btcd/apiserver/database"
|
||||||
|
"github.com/daglabs/btcd/apiserver/jsonrpc"
|
||||||
"github.com/daglabs/btcd/apiserver/models"
|
"github.com/daglabs/btcd/apiserver/models"
|
||||||
"github.com/daglabs/btcd/apiserver/utils"
|
"github.com/daglabs/btcd/apiserver/utils"
|
||||||
|
"github.com/daglabs/btcd/btcjson"
|
||||||
"github.com/daglabs/btcd/util/daghash"
|
"github.com/daglabs/btcd/util/daghash"
|
||||||
|
"github.com/daglabs/btcd/wire"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,7 +52,7 @@ func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerErro
|
|||||||
|
|
||||||
db, err := database.DB()
|
db, err := database.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
tx := &models.Transaction{}
|
tx := &models.Transaction{}
|
||||||
@ -72,7 +77,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
|
|||||||
|
|
||||||
db, err := database.DB()
|
db, err := database.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
txs := []*models.Transaction{}
|
txs := []*models.Transaction{}
|
||||||
@ -102,7 +107,7 @@ func GetTransactionsByAddressHandler(address string, skip uint64, limit uint64)
|
|||||||
func GetUTXOsByAddressHandler(address string) (interface{}, *utils.HandlerError) {
|
func GetUTXOsByAddressHandler(address string) (interface{}, *utils.HandlerError) {
|
||||||
db, err := database.DB()
|
db, err := database.DB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, utils.NewInternalServerHandlerError(err.Error())
|
return nil, utils.NewHandlerError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
|
||||||
}
|
}
|
||||||
|
|
||||||
var transactionOutputs []*models.TransactionOutput
|
var transactionOutputs []*models.TransactionOutput
|
||||||
@ -135,3 +140,45 @@ func addTxPreloadedFields(query *gorm.DB) *gorm.DB {
|
|||||||
Preload("TransactionInputs.TransactionOutput.Transaction").
|
Preload("TransactionInputs.TransactionOutput.Transaction").
|
||||||
Preload("TransactionInputs.TransactionOutput.Address")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
db.SetLogger(gormLogger{})
|
db.SetLogger(gormLogger{})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,11 @@ package jsonrpc
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/daglabs/btcd/apiserver/config"
|
"github.com/daglabs/btcd/apiserver/config"
|
||||||
"github.com/daglabs/btcd/util/daghash"
|
"github.com/daglabs/btcd/util/daghash"
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"github.com/daglabs/btcd/rpcclient"
|
"github.com/daglabs/btcd/rpcclient"
|
||||||
"github.com/daglabs/btcd/util"
|
"github.com/daglabs/btcd/util"
|
||||||
@ -67,6 +69,7 @@ func Connect(cfg *config.Config) error {
|
|||||||
User: cfg.RPCUser,
|
User: cfg.RPCUser,
|
||||||
Pass: cfg.RPCPassword,
|
Pass: cfg.RPCPassword,
|
||||||
DisableTLS: cfg.DisableTLS,
|
DisableTLS: cfg.DisableTLS,
|
||||||
|
RequestTimeout: time.Second * 5,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !cfg.DisableTLS {
|
if !cfg.DisableTLS {
|
||||||
|
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
@ -30,24 +31,37 @@ const (
|
|||||||
defaultGetBlocksOrder = controllers.OrderAscending
|
defaultGetBlocksOrder = controllers.OrderAscending
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeHandler(
|
type handlerFunc func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string, requestBody []byte) (
|
||||||
handler func(ctx *utils.APIServerContext, routeParams map[string]string, queryParams map[string]string) (
|
interface{}, *utils.HandlerError)
|
||||||
interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
|
|
||||||
|
func makeHandler(handler handlerFunc) func(http.ResponseWriter, *http.Request) {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := utils.ToAPIServerContext(r.Context())
|
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())
|
flattenedQueryParams, hErr := flattenQueryParams(r.URL.Query())
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
sendErr(ctx, w, hErr)
|
sendErr(ctx, w, hErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams)
|
|
||||||
|
response, hErr := handler(ctx, mux.Vars(r), flattenedQueryParams, requestBody)
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
sendErr(ctx, w, hErr)
|
sendErr(ctx, w, hErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if response != nil {
|
||||||
sendJSONResponse(w, response)
|
sendJSONResponse(w, response)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) {
|
func flattenQueryParams(queryParams map[string][]string) (map[string]string, *utils.HandlerError) {
|
||||||
@ -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 {
|
return struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}{
|
}{
|
||||||
@ -133,6 +147,11 @@ func addRoutes(router *mux.Router) {
|
|||||||
"/fee-estimates",
|
"/fee-estimates",
|
||||||
makeHandler(getFeeEstimatesHandler)).
|
makeHandler(getFeeEstimatesHandler)).
|
||||||
Methods("GET")
|
Methods("GET")
|
||||||
|
|
||||||
|
router.HandleFunc(
|
||||||
|
"/transaction",
|
||||||
|
makeHandler(postTransactionHandler)).
|
||||||
|
Methods("POST")
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertQueryParamToInt(queryParams map[string]string, param string, defaultValue int) (int, *utils.HandlerError) {
|
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
|
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])
|
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])
|
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)
|
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
return nil, hErr
|
return nil, hErr
|
||||||
@ -167,25 +192,34 @@ func getTransactionsByAddressHandler(_ *utils.APIServerContext, routeParams map[
|
|||||||
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, 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))
|
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])
|
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])
|
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()
|
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)
|
skip, hErr := convertQueryParamToInt(queryParams, queryParamSkip, 0)
|
||||||
if hErr != nil {
|
if hErr != nil {
|
||||||
return nil, hErr
|
return nil, hErr
|
||||||
@ -203,3 +237,8 @@ func getBlocksHandler(_ *utils.APIServerContext, _ map[string]string, queryParam
|
|||||||
}
|
}
|
||||||
return controllers.GetBlocksHandler(order, uint64(skip), uint64(limit))
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gorilla/handlers"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/handlers"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
const gracefulShutdownTimeout = 30 * time.Second
|
const gracefulShutdownTimeout = 30 * time.Second
|
||||||
@ -26,6 +27,7 @@ func Start(listenAddr string) func() {
|
|||||||
spawn(func() {
|
spawn(func() {
|
||||||
log.Errorf("%s", httpServer.ListenAndServe())
|
log.Errorf("%s", httpServer.ListenAndServe())
|
||||||
})
|
})
|
||||||
|
|
||||||
return func() {
|
return func() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
@ -3418,7 +3418,7 @@ func handleSendRawTransaction(s *Server, cmd interface{}, closeChan <-chan struc
|
|||||||
tx.ID(), err)
|
tx.ID(), err)
|
||||||
}
|
}
|
||||||
return nil, &btcjson.RPCError{
|
return nil, &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCDeserialization,
|
Code: btcjson.ErrRPCVerify,
|
||||||
Message: "TX rejected: " + err.Error(),
|
Message: "TX rejected: " + err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user