Merge PR #3954: Tx Broadcasting Sync by Default

This commit is contained in:
Alexander Bezobchuk 2019-03-25 20:54:23 -04:00 committed by GitHub
parent 5c2077c150
commit 2b43e25d55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 158 additions and 90 deletions

View File

@ -0,0 +1,3 @@
#3875 Replace `async` flag with `--broadcast-mode` flag where the default
value is `sync`. The `block` mode should not be used. The REST client now
uses `mode` parameter instead of the `return` parameter.

View File

@ -661,7 +661,7 @@ func (app *BaseApp) getContextForTx(mode runTxMode, txBytes []byte) (ctx sdk.Con
// runMsgs iterates through all the messages and executes them.
func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (result sdk.Result) {
idxlogs := make([]sdk.ABCIMessageLog, 0, len(msgs)) // a list of JSON-encoded logs with msg index
idxLogs := make([]sdk.ABCIMessageLog, 0, len(msgs)) // a list of JSON-encoded logs with msg index
var data []byte // NOTE: we just append them all (?!)
var tags sdk.Tags // also just append them all
@ -695,7 +695,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
// stop execution and return on first failed message
if !msgResult.IsOK() {
idxLog.Success = false
idxlogs = append(idxlogs, idxLog)
idxLogs = append(idxLogs, idxLog)
code = msgResult.Code
codespace = msgResult.Codespace
@ -703,10 +703,10 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
}
idxLog.Success = true
idxlogs = append(idxlogs, idxLog)
idxLogs = append(idxLogs, idxLog)
}
logJSON := codec.Cdc.MustMarshalJSON(idxlogs)
logJSON := codec.Cdc.MustMarshalJSON(idxLogs)
result = sdk.Result{
Code: code,
Codespace: codespace,
@ -719,7 +719,7 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (re
return result
}
// Returns the applicantion's deliverState if app is in runTxModeDeliver,
// Returns the applications's deliverState if app is in runTxModeDeliver,
// otherwise it returns the application's checkstate.
func (app *BaseApp) getState(mode runTxMode) *state {
if mode == runTxModeCheck || mode == runTxModeSimulate {
@ -829,7 +829,7 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte, tx sdk.Tx) (result sdk
// performance benefits, but it'll be more difficult to get right.
anteCtx, msCache = app.cacheTxContext(ctx, txBytes)
newCtx, result, abort := app.anteHandler(anteCtx, tx, (mode == runTxModeSimulate))
newCtx, result, abort := app.anteHandler(anteCtx, tx, mode == runTxModeSimulate)
if !newCtx.IsZero() {
// At this point, newCtx.MultiStore() is cache-wrapped, or something else
// replaced by the ante handler. We want the original multistore, not one

View File

@ -18,14 +18,11 @@ const (
flagGet = "get"
)
var configDefaults map[string]string
func init() {
configDefaults = map[string]string{
"chain-id": "",
"output": "text",
"node": "tcp://localhost:26657",
}
var configDefaults = map[string]string{
"chain-id": "",
"output": "text",
"node": "tcp://localhost:26657",
"broadcast-mode": "sync",
}
// ConfigCmd returns a CLI command to interactively create a
@ -56,13 +53,13 @@ func runConfigCmd(cmd *cobra.Command, args []string) error {
return fmt.Errorf("wrong number of arguments")
}
// Load configuration
// load configuration
tree, err := loadConfigFile(cfgFile)
if err != nil {
return err
}
// Print the config and exit
// print the config and exit
if len(args) == 0 {
s, err := tree.ToTomlString()
if err != nil {
@ -73,45 +70,54 @@ func runConfigCmd(cmd *cobra.Command, args []string) error {
}
key := args[0]
// Get value action
// get config value for a given key
if getAction {
switch key {
case "trace", "trust-node", "indent":
fmt.Println(tree.GetDefault(key, false).(bool))
default:
if defaultValue, ok := configDefaults[key]; ok {
fmt.Println(tree.GetDefault(key, defaultValue).(string))
return nil
}
return errUnknownConfigKey(key)
}
return nil
}
// Set value action
if len(args) != 2 {
return fmt.Errorf("wrong number of arguments")
}
value := args[1]
// set config value for a given key
switch key {
case "chain-id", "output", "node":
case "chain-id", "output", "node", "broadcast-mode":
tree.Set(key, value)
case "trace", "trust-node", "indent":
boolVal, err := strconv.ParseBool(value)
if err != nil {
return err
}
tree.Set(key, boolVal)
default:
return errUnknownConfigKey(key)
}
// Save configuration to disk
// save configuration to disk
if err := saveConfigFile(cfgFile, tree); err != nil {
return err
}
fmt.Fprintf(os.Stderr, "configuration saved to %s\n", cfgFile)
fmt.Fprintf(os.Stderr, "configuration saved to %s\n", cfgFile)
return nil
}

View File

@ -3,6 +3,7 @@ package context
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
)
@ -11,29 +12,36 @@ import (
// an intermediate structure which is logged if the context has a logger
// defined.
func (ctx CLIContext) BroadcastTx(txBytes []byte) (res sdk.TxResponse, err error) {
if ctx.Async {
if res, err = ctx.BroadcastTxAsync(txBytes); err != nil {
return
}
return
switch ctx.BroadcastMode {
case client.BroadcastSync:
res, err = ctx.BroadcastTxSync(txBytes)
case client.BroadcastAsync:
res, err = ctx.BroadcastTxAsync(txBytes)
case client.BroadcastBlock:
res, err = ctx.BroadcastTxCommit(txBytes)
default:
return sdk.TxResponse{}, fmt.Errorf("unsupported return type %s; supported types: sync, async, block", ctx.BroadcastMode)
}
if res, err = ctx.BroadcastTxAndAwaitCommit(txBytes); err != nil {
return
}
return
return res, err
}
// BroadcastTxAndAwaitCommit broadcasts transaction bytes to a Tendermint node
// and waits for a commit.
func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (sdk.TxResponse, error) {
// BroadcastTxCommit broadcasts transaction bytes to a Tendermint node and
// waits for a commit.
//
// NOTE: This should ideally not be used as the request may timeout but the tx
// may still be included in a block. Use BroadcastTxAsync or BroadcastTxSync
// instead.
func (ctx CLIContext) BroadcastTxCommit(txBytes []byte) (sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return sdk.TxResponse{}, err
}
res, err := node.BroadcastTxCommit(tx)
res, err := node.BroadcastTxCommit(txBytes)
if err != nil {
return sdk.NewResponseFormatBroadcastTxCommit(res), err
}
@ -46,35 +54,29 @@ func (ctx CLIContext) BroadcastTxAndAwaitCommit(tx []byte) (sdk.TxResponse, erro
return sdk.NewResponseFormatBroadcastTxCommit(res), fmt.Errorf(res.DeliverTx.Log)
}
return sdk.NewResponseFormatBroadcastTxCommit(res), err
return sdk.NewResponseFormatBroadcastTxCommit(res), nil
}
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node synchronously.
func (ctx CLIContext) BroadcastTxSync(tx []byte) (sdk.TxResponse, error) {
// BroadcastTxSync broadcasts transaction bytes to a Tendermint node
// synchronously (i.e. returns after CheckTx execution).
func (ctx CLIContext) BroadcastTxSync(txBytes []byte) (sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return sdk.TxResponse{}, err
}
res, err := node.BroadcastTxSync(tx)
if err != nil {
return sdk.NewResponseFormatBroadcastTx(res), err
}
res, err := node.BroadcastTxSync(txBytes)
return sdk.NewResponseFormatBroadcastTx(res), err
}
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node asynchronously.
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (sdk.TxResponse, error) {
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
// asynchronously (i.e. returns immediately).
func (ctx CLIContext) BroadcastTxAsync(txBytes []byte) (sdk.TxResponse, error) {
node, err := ctx.GetNode()
if err != nil {
return sdk.TxResponse{}, err
}
res, err := node.BroadcastTxAsync(tx)
if err != nil {
return sdk.NewResponseFormatBroadcastTx(res), err
}
res, err := node.BroadcastTxAsync(txBytes)
return sdk.NewResponseFormatBroadcastTx(res), err
}

View File

@ -44,7 +44,7 @@ type CLIContext struct {
AccountStore string
TrustNode bool
UseLedger bool
Async bool
BroadcastMode string
PrintResponse bool
Verifier tmlite.Verifier
VerifierHome string
@ -90,7 +90,7 @@ func NewCLIContext() CLIContext {
Height: viper.GetInt64(client.FlagHeight),
TrustNode: viper.GetBool(client.FlagTrustNode),
UseLedger: viper.GetBool(client.FlagUseLedger),
Async: viper.GetBool(client.FlagAsync),
BroadcastMode: viper.GetString(client.FlagBroadcastMode),
PrintResponse: viper.GetBool(client.FlagPrintResponse),
Verifier: verifier,
Simulate: viper.GetBool(client.FlagDryRun),
@ -248,6 +248,13 @@ func (ctx CLIContext) WithFromAddress(addr sdk.AccAddress) CLIContext {
return ctx
}
// WithBroadcastMode returns a copy of the context with an updated broadcast
// mode.
func (ctx CLIContext) WithBroadcastMode(mode string) CLIContext {
ctx.BroadcastMode = mode
return ctx
}
// PrintOutput prints output while respecting output and indent flags
// NOTE: pass in marshalled structs that have been unmarshaled
// because this function will panic on marshaling errors

View File

@ -18,11 +18,20 @@ const (
DefaultGasLimit = 200000
GasFlagAuto = "auto"
// BroadcastBlock defines a tx broadcasting mode where the client waits for
// the tx to be committed in a block.
BroadcastBlock = "block"
// BroadcastSync defines a tx broadcasting mode where the client waits for
// a CheckTx execution response only.
BroadcastSync = "sync"
// BroadcastAsync defines a tx broadcasting mode where the client returns
// immediately.
BroadcastAsync = "async"
FlagUseLedger = "ledger"
FlagChainID = "chain-id"
FlagNode = "node"
FlagHeight = "height"
FlagGas = "gas"
FlagGasAdjustment = "gas-adjustment"
FlagTrustNode = "trust-node"
FlagFrom = "from"
@ -32,7 +41,7 @@ const (
FlagMemo = "memo"
FlagFees = "fees"
FlagGasPrices = "gas-prices"
FlagAsync = "async"
FlagBroadcastMode = "broadcast-mode"
FlagPrintResponse = "print-response"
FlagDryRun = "dry-run"
FlagGenerateOnly = "generate-only"
@ -80,7 +89,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
c.Flags().String(FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().Bool(FlagUseLedger, false, "Use a connected Ledger device")
c.Flags().Float64(FlagGasAdjustment, DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
c.Flags().Bool(FlagAsync, false, "broadcast transactions asynchronously")
c.Flags().StringP(FlagBroadcastMode, "b", BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
c.Flags().Bool(FlagPrintResponse, true, "return tx response (only works with async = false)")
c.Flags().Bool(FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)")
c.Flags().Bool(FlagDryRun, false, "ignore the --gas flag and perform a simulation of a transaction, but don't broadcast it")

View File

@ -250,14 +250,14 @@ paths:
parameters:
- in: body
name: txBroadcast
description: The tx must be a signed StdTx. The supported return types includes `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away).
description: The tx must be a signed StdTx. The supported broadcast modes include `"block"`(return after tx commit), `"sync"`(return afer CheckTx) and `"async"`(return right away).
required: true
schema:
type: object
properties:
tx:
$ref: "#/definitions/StdTx"
return:
mode:
type: string
example: block
responses:

View File

@ -651,7 +651,7 @@ func getAccount(t *testing.T, port string, addr sdk.AccAddress) auth.Account {
// POST /tx/broadcast Send a signed Tx
func doBroadcast(t *testing.T, port string, tx auth.StdTx) (*http.Response, string) {
txReq := clienttx.BroadcastReq{Tx: tx, Return: "block"}
txReq := clienttx.BroadcastReq{Tx: tx, Mode: "block"}
req, err := cdc.MarshalJSON(txReq)
require.Nil(t, err)

View File

@ -18,19 +18,10 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
)
const (
// Returns with the response from CheckTx.
flagSync = "sync"
// Returns right away, with no response
flagAsync = "async"
// Only returns error if mempool.BroadcastTx errs (ie. problem with the app) or if we timeout waiting for tx to commit.
flagBlock = "block"
)
// BroadcastReq defines a tx broadcasting request.
type BroadcastReq struct {
Tx auth.StdTx `json:"tx"`
Return string `json:"return"`
Tx auth.StdTx `json:"tx"`
Mode string `json:"mode"`
}
// BroadcastTxRequest implements a tx broadcasting handler that is responsible
@ -58,23 +49,9 @@ func BroadcastTxRequest(cliCtx context.CLIContext, cdc *codec.Codec) http.Handle
return
}
var res interface{}
switch req.Return {
case flagBlock:
res, err = cliCtx.BroadcastTx(txBytes)
case flagSync:
res, err = cliCtx.BroadcastTxSync(txBytes)
case flagAsync:
res, err = cliCtx.BroadcastTxAsync(txBytes)
default:
rest.WriteErrorResponse(w, http.StatusInternalServerError,
"unsupported return type. supported types: block, sync, async")
return
}
cliCtx = cliCtx.WithBroadcastMode(req.Mode)
res, err := cliCtx.BroadcastTx(txBytes)
if err != nil {
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return

View File

@ -80,6 +80,7 @@ func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIConte
} else {
json = cliCtx.Codec.MustMarshalJSON(stdSignMsg)
}
fmt.Fprintf(os.Stderr, "%s\n\n", json)
buf := client.BufferStdin()
@ -103,8 +104,11 @@ func CompleteAndBroadcastTxCLI(txBldr authtxb.TxBuilder, cliCtx context.CLIConte
// broadcast to a Tendermint node
res, err := cliCtx.BroadcastTx(txBytes)
cliCtx.PrintOutput(res) // nolint:errcheck
return err
if err != nil {
return err
}
return cliCtx.PrintOutput(res)
}
// EnrichWithGas calculates the gas estimate that would be consumed by the

View File

@ -965,6 +965,7 @@ func TestGaiaCLIConfig(t *testing.T) {
node := fmt.Sprintf("%s:%s", f.RPCAddr, f.Port)
// Set available configuration options
f.CLIConfig("broadcast-mode", "block")
f.CLIConfig("node", node)
f.CLIConfig("output", "text")
f.CLIConfig("trust-node", "true")
@ -974,7 +975,8 @@ func TestGaiaCLIConfig(t *testing.T) {
config, err := ioutil.ReadFile(path.Join(f.GCLIHome, "config", "config.toml"))
require.NoError(t, err)
expectedConfig := fmt.Sprintf(`chain-id = "%s"
expectedConfig := fmt.Sprintf(`broadcast-mode = "block"
chain-id = "%s"
indent = true
node = "%s"
output = "text"

View File

@ -129,7 +129,9 @@ func InitFixtures(t *testing.T) (f *Fixtures) {
// NOTE: GDInit sets the ChainID
f.GDInit(keyFoo)
f.CLIConfig("chain-id", f.ChainID)
f.CLIConfig("broadcast-mode", "block")
// start an account with tokens
f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins)

View File

@ -128,6 +128,19 @@ gaiacli keys show --multisig-threshold K name1 name2 name3 [...]
For more information regarding how to generate, sign and broadcast transactions with a
multi signature account see [Multisig Transactions](#multisig-transactions).
### Tx Broadcasting
When broadcasting transactions, `gaiacli` accepts a `--broadcast-mode` flag. This
flag can have a value of `sync` (default), `async`, or `block`, where `sync` makes
the client return a CheckTx response, `async` makes the client return immediately,
and `block` makes the client wait for the tx to be committed (or timing out).
It is important to note that the `block` mode should **not** be used in most
circumstances. This is because broadcasting can timeout but the tx may still be
included in a block. This can result in many undesirable situations. Therefor, it
is best to use `sync` or `async` and query by tx hash to determine when the tx
is included in a block.
### Fees & Gas
Each transaction may either supply fees or gas prices, but not both.

View File

@ -68,6 +68,7 @@ type TxResponse struct {
TxHash string `json:"txhash"`
Code uint32 `json:"code,omitempty"`
Data []byte `json:"data,omitempty"`
RawLog string `json:"raw_log,omitempty"`
Logs ABCIMessageLogs `json:"logs,omitempty"`
Info string `json:"info,omitempty"`
GasWanted int64 `json:"gas_wanted,omitempty"`
@ -90,6 +91,7 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse {
Height: res.Height,
Code: res.TxResult.Code,
Data: res.TxResult.Data,
RawLog: res.TxResult.Log,
Logs: parsedLogs,
Info: res.TxResult.Info,
GasWanted: res.TxResult.GasWanted,
@ -99,8 +101,44 @@ func NewResponseResultTx(res *ctypes.ResultTx, tx Tx) TxResponse {
}
}
// NewResponseFormatBroadcastTxCommit returns a TxResponse given a ResultBroadcastTxCommit from tendermint
// NewResponseFormatBroadcastTxCommit returns a TxResponse given a
// ResultBroadcastTxCommit from tendermint.
func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if !res.CheckTx.IsOK() {
return newTxResponseCheckTx(res)
}
return newTxResponseDeliverTx(res)
}
func newTxResponseCheckTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if res == nil {
return TxResponse{}
}
var txHash string
if res.Hash != nil {
txHash = res.Hash.String()
}
parsedLogs, _ := ParseABCILogs(res.CheckTx.Log)
return TxResponse{
Height: res.Height,
TxHash: txHash,
Code: res.CheckTx.Code,
Data: res.CheckTx.Data,
RawLog: res.CheckTx.Log,
Logs: parsedLogs,
Info: res.CheckTx.Info,
GasWanted: res.CheckTx.GasWanted,
GasUsed: res.CheckTx.GasUsed,
Tags: TagsToStringTags(res.CheckTx.Tags),
Codespace: res.CheckTx.Codespace,
}
}
func newTxResponseDeliverTx(res *ctypes.ResultBroadcastTxCommit) TxResponse {
if res == nil {
return TxResponse{}
}
@ -117,6 +155,7 @@ func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxR
TxHash: txHash,
Code: res.DeliverTx.Code,
Data: res.DeliverTx.Data,
RawLog: res.DeliverTx.Log,
Logs: parsedLogs,
Info: res.DeliverTx.Info,
GasWanted: res.DeliverTx.GasWanted,
@ -124,7 +163,6 @@ func NewResponseFormatBroadcastTxCommit(res *ctypes.ResultBroadcastTxCommit) TxR
Tags: TagsToStringTags(res.DeliverTx.Tags),
Codespace: res.DeliverTx.Codespace,
}
}
// NewResponseFormatBroadcastTx returns a TxResponse given a ResultBroadcastTx from tendermint
@ -138,6 +176,7 @@ func NewResponseFormatBroadcastTx(res *ctypes.ResultBroadcastTx) TxResponse {
return TxResponse{
Code: res.Code,
Data: res.Data.Bytes(),
RawLog: res.Log,
Logs: parsedLogs,
TxHash: res.Hash.String(),
}
@ -163,6 +202,10 @@ func (r TxResponse) String() string {
sb.WriteString(fmt.Sprintf(" Data: %s\n", string(r.Data)))
}
if r.RawLog != "" {
sb.WriteString(fmt.Sprintf(" Raw Log: %s\n", r.RawLog))
}
if r.Logs != nil {
sb.WriteString(fmt.Sprintf(" Logs: %s\n", r.Logs))
}