Finish remaining x/bank REST handlers

This commit is contained in:
Aleksandr Bezobchuk 2020-03-25 14:23:34 -04:00
parent 0e83e328d0
commit 3e0cf99e81
No known key found for this signature in database
GPG Key ID: 7DAC30FBD99879B0
6 changed files with 169 additions and 30 deletions

View File

@ -80,31 +80,31 @@ func (f Factory) AccountRetriever() AccountRetriever { return f.accountRetriever
// using the gas from the simulation results
func (f Factory) SimulateAndExecute() bool { return f.simulateAndExecute }
// WithTxGenerator returns a copy of the Builder with an updated Generator.
// WithTxGenerator returns a copy of the Factory with an updated Generator.
func (f Factory) WithTxGenerator(g Generator) Factory {
f.txGenerator = g
return f
}
// WithAccountRetriever returns a copy of the Builder with an updated AccountRetriever.
// WithAccountRetriever returns a copy of the Factory with an updated AccountRetriever.
func (f Factory) WithAccountRetriever(ar AccountRetriever) Factory {
f.accountRetriever = ar
return f
}
// WithChainID returns a copy of the Builder with an updated chainID.
// WithChainID returns a copy of the Factory with an updated chainID.
func (f Factory) WithChainID(chainID string) Factory {
f.chainID = chainID
return f
}
// WithGas returns a copy of the Builder with an updated gas value.
// WithGas returns a copy of the Factory with an updated gas value.
func (f Factory) WithGas(gas uint64) Factory {
f.gas = gas
return f
}
// WithFees returns a copy of the Builder with an updated fee.
// WithFees returns a copy of the Factory with an updated fee.
func (f Factory) WithFees(fees string) Factory {
parsedFees, err := sdk.ParseCoins(fees)
if err != nil {
@ -115,7 +115,7 @@ func (f Factory) WithFees(fees string) Factory {
return f
}
// WithGasPrices returns a copy of the Builder with updated gas prices.
// WithGasPrices returns a copy of the Factory with updated gas prices.
func (f Factory) WithGasPrices(gasPrices string) Factory {
parsedGasPrices, err := sdk.ParseDecCoins(gasPrices)
if err != nil {
@ -126,26 +126,39 @@ func (f Factory) WithGasPrices(gasPrices string) Factory {
return f
}
// WithKeybase returns a copy of the Builder with updated Keybase.
// WithKeybase returns a copy of the Factory with updated Keybase.
func (f Factory) WithKeybase(keybase keys.Keybase) Factory {
f.keybase = keybase
return f
}
// WithSequence returns a copy of the Builder with an updated sequence number.
// WithSequence returns a copy of the Factory with an updated sequence number.
func (f Factory) WithSequence(sequence uint64) Factory {
f.sequence = sequence
return f
}
// WithMemo returns a copy of the Builder with an updated memo.
// WithMemo returns a copy of the Factory with an updated memo.
func (f Factory) WithMemo(memo string) Factory {
f.memo = strings.TrimSpace(memo)
return f
}
// WithAccountNumber returns a copy of the Builder with an updated account number.
// WithAccountNumber returns a copy of the Factory with an updated account number.
func (f Factory) WithAccountNumber(accnum uint64) Factory {
f.accountNumber = accnum
return f
}
// WithGasAdjustment returns a copy of the Factory with an updated gas adjustment.
func (f Factory) WithGasAdjustment(gasAdj float64) Factory {
f.gasAdjustment = gasAdj
return f
}
// WithSimulateAndExecute returns a copy of the Factory with an updated gas
// simulation value.
func (f Factory) WithSimulateAndExecute(sim bool) Factory {
f.simulateAndExecute = sim
return f
}

View File

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"github.com/spf13/viper"
@ -16,7 +17,9 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)
type (
@ -71,12 +74,7 @@ func GenerateTx(ctx context.CLIContext, txf Factory, msgs ...sdk.Msg) error {
return errors.New("cannot estimate gas in offline mode")
}
txBytes, err := BuildSimTx(txf, msgs...)
if err != nil {
return err
}
_, adjusted, err := CalculateGas(ctx.QueryWithData, txBytes, txf.GasAdjustment())
_, adjusted, err := CalculateGas(ctx.QueryWithData, txf, msgs...)
if err != nil {
return err
}
@ -103,12 +101,7 @@ func BroadcastTx(ctx context.CLIContext, txf Factory, msgs ...sdk.Msg) error {
}
if txf.SimulateAndExecute() || ctx.Simulate {
txBytes, err := BuildSimTx(txf, msgs...)
if err != nil {
return err
}
_, adjusted, err := CalculateGas(ctx.QueryWithData, txBytes, txf.GasAdjustment())
_, adjusted, err := CalculateGas(ctx.QueryWithData, txf, msgs...)
if err != nil {
return err
}
@ -156,6 +149,70 @@ func BroadcastTx(ctx context.CLIContext, txf Factory, msgs ...sdk.Msg) error {
return ctx.Print(res)
}
// WriteGeneratedTxResponse writes a generated unsigned transaction to the
// provided http.ResponseWriter. It will simulate gas costs if requested by the
// BaseReq. Upon any error, the error will be written to the http.ResponseWriter.
func WriteGeneratedTxResponse(
ctx context.CLIContext, w http.ResponseWriter, txg Generator, br rest.BaseReq, msgs ...sdk.Msg,
) {
gasAdj, ok := rest.ParseFloat64OrReturnBadRequest(w, br.GasAdjustment, flags.DefaultGasAdjustment)
if !ok {
return
}
simAndExec, gas, err := flags.ParseGas(br.Gas)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
txf := Factory{fees: br.Fees, gasPrices: br.GasPrices}.
WithAccountNumber(br.AccountNumber).
WithSequence(br.Sequence).
WithGas(gas).
WithGasAdjustment(gasAdj).
WithMemo(br.Memo).
WithChainID(br.ChainID).
WithSimulateAndExecute(br.Simulate)
if br.Simulate || simAndExec {
if gasAdj < 0 {
rest.WriteErrorResponse(w, http.StatusBadRequest, types.ErrorInvalidGasAdjustment.Error())
return
}
_, adjusted, err := CalculateGas(ctx.QueryWithData, txf, msgs...)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
txf = txf.WithGas(adjusted)
if br.Simulate {
rest.WriteSimulationResponse(w, ctx.Marshaler, txf.Gas())
return
}
}
tx, err := BuildUnsignedTx(txf, msgs...)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
output, err := ctx.Marshaler.MarshalJSON(tx)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write(output)
}
// BuildUnsignedTx builds a transaction to be signed given a set of messages. The
// transaction is initially created via the provided factory's generator. Once
// created, the fee, memo, and messages are set.
@ -209,9 +266,14 @@ func BuildSimTx(txf Factory, msgs ...sdk.Msg) ([]byte, error) {
// CalculateGas simulates the execution of a transaction and returns the
// simulation response obtained by the query and the adjusted gas amount.
func CalculateGas(
queryFunc func(string, []byte) ([]byte, int64, error), txBytes []byte, adjustment float64,
queryFunc func(string, []byte) ([]byte, int64, error), txf Factory, msgs ...sdk.Msg,
) (sdk.SimulationResponse, uint64, error) {
txBytes, err := BuildSimTx(txf, msgs...)
if err != nil {
return sdk.SimulationResponse{}, 0, err
}
bz, _, err := queryFunc("/app/simulate", txBytes)
if err != nil {
return sdk.SimulationResponse{}, 0, err
@ -222,7 +284,7 @@ func CalculateGas(
return sdk.SimulationResponse{}, 0, err
}
return simRes, uint64(adjustment * float64(simRes.GasUsed)), nil
return simRes, uint64(txf.GasAdjustment() * float64(simRes.GasUsed)), nil
}
// PrepareFactory ensures the account defined by ctx.GetFromAddress() exists and

View File

@ -53,10 +53,11 @@ func TestCalculateGas(t *testing.T) {
for _, tc := range testCases {
stc := tc
txf := tx.Factory{}.WithChainID("test-chain").WithTxGenerator(std.TxGenerator{})
t.Run(stc.name, func(t *testing.T) {
queryFunc := makeQueryFunc(stc.args.queryFuncGasUsed, stc.args.queryFuncWantErr)
simRes, gotAdjusted, err := tx.CalculateGas(queryFunc, []byte(""), stc.args.adjustment)
simRes, gotAdjusted, err := tx.CalculateGas(queryFunc, txf.WithGasAdjustment(stc.args.adjustment))
if stc.expPass {
require.NoError(t, err)
require.Equal(t, simRes.GasInfo.GasUsed, stc.wantEstimate)

View File

@ -119,16 +119,16 @@ func (br BaseReq) ValidateBasic(w http.ResponseWriter) bool {
return true
}
// ReadRESTReq reads and unmarshals a Request's body to the the BaseReq stuct.
// ReadRESTReq reads and unmarshals a Request's body to the the BaseReq struct.
// Writes an error response to ResponseWriter and returns true if errors occurred.
func ReadRESTReq(w http.ResponseWriter, r *http.Request, cdc *codec.Codec, req interface{}) bool {
func ReadRESTReq(w http.ResponseWriter, r *http.Request, m codec.JSONMarshaler, req interface{}) bool {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return false
}
err = cdc.UnmarshalJSON(body, req)
err = m.UnmarshalJSON(body, req)
if err != nil {
WriteErrorResponse(w, http.StatusBadRequest, fmt.Sprintf("failed to decode JSON payload: %s", err))
return false
@ -158,9 +158,10 @@ func WriteErrorResponse(w http.ResponseWriter, status int, err string) {
// WriteSimulationResponse prepares and writes an HTTP
// response for transactions simulations.
func WriteSimulationResponse(w http.ResponseWriter, cdc *codec.Codec, gas uint64) {
func WriteSimulationResponse(w http.ResponseWriter, m codec.JSONMarshaler, gas uint64) {
gasEst := GasEstimateResponse{GasEstimate: gas}
resp, err := cdc.MarshalJSON(gasEst)
resp, err := m.MarshalJSON(gasEst)
if err != nil {
WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return

View File

@ -4,8 +4,23 @@ import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
)
// RegisterHandlers registers all x/bank transaction and query HTTP REST handlers
// on the provided mux router.
func RegisterHandlers(ctx context.CLIContext, m codec.Marshaler, txg tx.Generator, r *mux.Router) {
r.HandleFunc("/bank/accounts/{address}/transfers", NewSendRequestHandlerFn(ctx, m, txg)).Methods("POST")
r.HandleFunc("/bank/balances/{address}", QueryBalancesRequestHandlerFn(ctx)).Methods("GET")
}
// ---------------------------------------------------------------------------
// Deprecated
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ---------------------------------------------------------------------------
// RegisterRoutes - Central function to define routes that get registered by the main application
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
r.HandleFunc("/bank/accounts/{address}/transfers", SendRequestHandlerFn(cliCtx)).Methods("POST")

View File

@ -6,6 +6,8 @@ import (
"github.com/gorilla/mux"
"github.com/cosmos/cosmos-sdk/client/context"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
@ -18,7 +20,52 @@ type SendReq struct {
Amount sdk.Coins `json:"amount" yaml:"amount"`
}
// NewSendRequestHandlerFn returns an HTTP REST handler for creating a MsgSend
// transaction.
func NewSendRequestHandlerFn(ctx context.CLIContext, m codec.Marshaler, txg tx.Generator) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx = ctx.WithMarshaler(m)
vars := mux.Vars(r)
bech32Addr := vars["address"]
toAddr, err := sdk.AccAddressFromBech32(bech32Addr)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
var req SendReq
if !rest.ReadRESTReq(w, r, ctx.Marshaler, &req) {
return
}
req.BaseReq = req.BaseReq.Sanitize()
if !req.BaseReq.ValidateBasic(w) {
return
}
fromAddr, err := sdk.AccAddressFromBech32(req.BaseReq.From)
if err != nil {
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
return
}
msg := types.NewMsgSend(fromAddr, toAddr, req.Amount)
tx.WriteGeneratedTxResponse(ctx, w, txg, req.BaseReq, msg)
}
}
// ---------------------------------------------------------------------------
// Deprecated
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ---------------------------------------------------------------------------
// SendRequestHandlerFn - http request handler to send coins to a address.
//
// TODO: Remove once client-side Protobuf migration has been completed.
// ref: https://github.com/cosmos/cosmos-sdk/issues/5864
func SendRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)