diff --git a/CHANGELOG.md b/CHANGELOG.md index 2801c932b7..ddc777b2e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ FEATURES: * Add CacheContext +FEATURES: + +* Add auto sequencing to client + ## 0.14.1 (April 9, 2018) BUG FIXES diff --git a/client/context/viper.go b/client/context/viper.go index 750a37c616..193fe9baa6 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -1,27 +1,80 @@ package context import ( + "encoding/json" + "fmt" "github.com/spf13/viper" + "io/ioutil" + tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" rpcclient "github.com/tendermint/tendermint/rpc/client" + tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/core" ) +// NewCoreContextFromViper - return a new context with parameters from the command line func NewCoreContextFromViper() core.CoreContext { nodeURI := viper.GetString(client.FlagNode) var rpc rpcclient.Client if nodeURI != "" { rpc = rpcclient.NewHTTP(nodeURI, "/websocket") } + chainID := viper.GetString(client.FlagChainID) + // if chain ID is not specified manually, read default chain ID + if chainID == "" { + def, err := defaultChainID() + if err != nil { + chainID = def + } + } return core.CoreContext{ - ChainID: viper.GetString(client.FlagChainID), + ChainID: chainID, Height: viper.GetInt64(client.FlagHeight), TrustNode: viper.GetBool(client.FlagTrustNode), FromAddressName: viper.GetString(client.FlagName), NodeURI: nodeURI, Sequence: viper.GetInt64(client.FlagSequence), Client: rpc, + Decoder: nil, + AccountStore: "main", } } + +// read chain ID from genesis file, if present +func defaultChainID() (string, error) { + cfg, err := tcmd.ParseConfig() + if err != nil { + return "", err + } + genesisFile := cfg.GenesisFile() + bz, err := ioutil.ReadFile(genesisFile) + if err != nil { + return "", err + } + var doc tmtypes.GenesisDoc + err = json.Unmarshal(bz, &doc) + if err != nil { + return "", err + } + return doc.ChainID, nil +} + +// EnsureSequence - automatically set sequence number if none provided +func EnsureSequence(ctx core.CoreContext) (core.CoreContext, error) { + if viper.IsSet(client.FlagSequence) { + return ctx, nil + } + from, err := ctx.GetFromAddress() + if err != nil { + return ctx, err + } + seq, err := ctx.NextSequence(from) + if err != nil { + return ctx, err + } + fmt.Printf("Defaulting to next sequence number: %d\n", seq) + ctx = ctx.WithSequence(seq) + return ctx, nil +} diff --git a/client/core/context.go b/client/core/context.go index 77d2dc549b..7f8d764324 100644 --- a/client/core/context.go +++ b/client/core/context.go @@ -2,6 +2,8 @@ package core import ( rpcclient "github.com/tendermint/tendermint/rpc/client" + + sdk "github.com/cosmos/cosmos-sdk/types" ) type CoreContext struct { @@ -12,40 +14,61 @@ type CoreContext struct { FromAddressName string Sequence int64 Client rpcclient.Client + Decoder sdk.AccountDecoder + AccountStore string } +// WithChainID - return a copy of the context with an updated chainID func (c CoreContext) WithChainID(chainID string) CoreContext { c.ChainID = chainID return c } +// WithHeight - eturn a copy of the context with an updated height func (c CoreContext) WithHeight(height int64) CoreContext { c.Height = height return c } +// WithTrustNode - return a copy of the context with an updated TrustNode flag func (c CoreContext) WithTrustNode(trustNode bool) CoreContext { c.TrustNode = trustNode return c } +// WithNodeURI - return a copy of the context with an updated node URI func (c CoreContext) WithNodeURI(nodeURI string) CoreContext { c.NodeURI = nodeURI c.Client = rpcclient.NewHTTP(nodeURI, "/websocket") return c } +// WithFromAddressName - return a copy of the context with an updated from address func (c CoreContext) WithFromAddressName(fromAddressName string) CoreContext { c.FromAddressName = fromAddressName return c } +// WithSequence - return a copy of the context with an updated sequence number func (c CoreContext) WithSequence(sequence int64) CoreContext { c.Sequence = sequence return c } +// WithClient - return a copy of the context with an updated RPC client instance func (c CoreContext) WithClient(client rpcclient.Client) CoreContext { c.Client = client return c } + +// WithDecoder - return a copy of the context with an updated Decoder +func (c CoreContext) WithDecoder(decoder sdk.AccountDecoder) CoreContext { + c.Decoder = decoder + return c +} + +// WithAccountStore - return a copy of the context with an updated AccountStore +func (c CoreContext) WithAccountStore(accountStore string) CoreContext { + c.AccountStore = accountStore + return c +} diff --git a/client/core/core.go b/client/core/core.go index a5c7b340c7..2a1b2736a2 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -91,6 +91,9 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w // build the Sign Messsage from the Standard Message chainID := ctx.ChainID + if chainID == "" { + return nil, errors.Errorf("Chain ID required but not specified") + } sequence := ctx.Sequence signMsg := sdk.StdSignMsg{ ChainID: chainID, @@ -137,6 +140,25 @@ func (ctx CoreContext) SignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Co return ctx.BroadcastTx(txBytes) } +// get the next sequence for the account address +func (c CoreContext) NextSequence(address []byte) (int64, error) { + if c.Decoder == nil { + return 0, errors.New("AccountDecoder required but not provided") + } + + res, err := c.Query(address, c.AccountStore) + if err != nil { + return 0, err + } + + account, err := c.Decoder(res) + if err != nil { + panic(err) + } + + return account.GetSequence(), nil +} + // get passphrase from std input func (ctx CoreContext) GetPassphraseFromStdin(name string) (pass string, err error) { buf := client.BufferStdin() diff --git a/examples/democoin/x/cool/commands/tx.go b/examples/democoin/x/cool/commands/tx.go index 8deaac405d..88c5e4e68a 100644 --- a/examples/democoin/x/cool/commands/tx.go +++ b/examples/democoin/x/cool/commands/tx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" ) @@ -24,7 +25,7 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { return errors.New("You must provide an answer") } - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) // get the from address from the name flag from, err := ctx.GetFromAddress() @@ -38,6 +39,12 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { // get account name name := viper.GetString(client.FlagName) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(name, msg, cdc) if err != nil { @@ -60,7 +67,7 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { return errors.New("You must provide an answer") } - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) // get the from address from the name flag from, err := ctx.GetFromAddress() @@ -71,6 +78,12 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { // get account name name := viper.GetString(client.FlagName) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // create the message msg := cool.NewSetTrendMsg(from, args[0]) diff --git a/examples/democoin/x/pow/commands/tx.go b/examples/democoin/x/pow/commands/tx.go index badbe39092..5fa11a4766 100644 --- a/examples/democoin/x/pow/commands/tx.go +++ b/examples/democoin/x/pow/commands/tx.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" ) func MineCmd(cdc *wire.Codec) *cobra.Command { @@ -24,7 +25,7 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { // get from address and parse arguments - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) from, err := ctx.GetFromAddress() if err != nil { @@ -53,6 +54,12 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { // get account name name := ctx.FromAddressName + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(name, msg, cdc) if err != nil { diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index aa0e17a910..56048262d7 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -36,7 +37,7 @@ type Commander struct { } func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(c.Cdc)) // get the from address from, err := ctx.GetFromAddress() @@ -62,6 +63,12 @@ func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { // build message msg := BuildMsg(from, to, coins) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.Cdc) if err != nil { diff --git a/x/ibc/commands/ibctx.go b/x/ibc/commands/ibctx.go index 689a983181..d17b40b21e 100644 --- a/x/ibc/commands/ibctx.go +++ b/x/ibc/commands/ibctx.go @@ -13,6 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/ibc" ) @@ -39,7 +40,7 @@ type sendCommander struct { } func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(c.cdc)) // get the from address from, err := ctx.GetFromAddress() @@ -53,6 +54,12 @@ func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error return err } + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // get password res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.cdc) if err != nil { diff --git a/x/simplestake/commands/commands.go b/x/simplestake/commands/commands.go index b2a057beed..ba66020283 100644 --- a/x/simplestake/commands/commands.go +++ b/x/simplestake/commands/commands.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/simplestake" ) @@ -94,7 +95,14 @@ func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error { } func (co commander) sendMsg(msg sdk.Msg) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(co.cdc)) + + // default to next sequence number if none provided + ctx, err := context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, co.cdc) if err != nil { return err diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index d1a399e19c..5cc9747b61 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -92,7 +93,14 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -129,7 +137,14 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgEditCandidacy(candidateAddr, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -165,7 +180,14 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -212,7 +234,14 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err