<!-- The default pull request template is for types feat, fix, or refactor. For other templates, add one of the following parameters to the url: - template=docs.md - template=other.md --> ## Description Closes: #10264 This PR makes 2 changes in the CLI: ### 1. Add an `--aux` flag to all tx command. ```bash simd tx bank send <from> <to> <amount> --aux (optional: --tip <tipAmt> --tipper <tipper>) ``` This will print an AuxSignerData instead of broadcasting the tx. ### 2. Add a new `aux-to-fee` subcommand ```bash simd tx aux-to-fee <aux_signer_data.json> ``` This takes the output of the previous command, and broadcasts a 2-signer tx. <!-- Add a description of the changes that this PR introduces and the files that are the most critical to review. --> --- ### Author Checklist *All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.* I have... - [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] added `!` to the type prefix if API or client breaking change - [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [ ] provided a link to the relevant issue or specification - [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules) - [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [ ] added a changelog entry to `CHANGELOG.md` - [ ] included comments for [documenting Go code](https://blog.golang.org/godoc) - [ ] updated the relevant documentation or specification - [ ] reviewed "Files changed" and left comments if necessary - [ ] confirmed all CI checks have passed ### Reviewers Checklist *All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.* I have... - [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title - [ ] confirmed `!` in the type prefix if API or client breaking change - [ ] confirmed all author checklist items have been addressed - [ ] reviewed state machine logic - [ ] reviewed API design and naming - [ ] reviewed documentation is accurate - [ ] reviewed tests and test coverage - [ ] manually tested (if applicable)
364 lines
9.9 KiB
Go
364 lines
9.9 KiB
Go
package client
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keyring"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
// Context implements a typical context created in SDK modules for transaction
|
|
// handling and queries.
|
|
type Context struct {
|
|
FromAddress sdk.AccAddress
|
|
Client rpcclient.Client
|
|
ChainID string
|
|
Codec codec.Codec
|
|
InterfaceRegistry codectypes.InterfaceRegistry
|
|
Input io.Reader
|
|
Keyring keyring.Keyring
|
|
KeyringOptions []keyring.Option
|
|
Output io.Writer
|
|
OutputFormat string
|
|
Height int64
|
|
HomeDir string
|
|
KeyringDir string
|
|
From string
|
|
BroadcastMode string
|
|
FromName string
|
|
SignModeStr string
|
|
UseLedger bool
|
|
Simulate bool
|
|
GenerateOnly bool
|
|
Offline bool
|
|
SkipConfirm bool
|
|
TxConfig TxConfig
|
|
AccountRetriever AccountRetriever
|
|
NodeURI string
|
|
FeePayer sdk.AccAddress
|
|
FeeGranter sdk.AccAddress
|
|
Viper *viper.Viper
|
|
|
|
// IsAux is true when the signer is an auxiliary signer (e.g. the tipper).
|
|
IsAux bool
|
|
|
|
// TODO: Deprecated (remove).
|
|
LegacyAmino *codec.LegacyAmino
|
|
}
|
|
|
|
// WithKeyring returns a copy of the context with an updated keyring.
|
|
func (ctx Context) WithKeyring(k keyring.Keyring) Context {
|
|
ctx.Keyring = k
|
|
return ctx
|
|
}
|
|
|
|
// WithKeyringOptions returns a copy of the context with an updated keyring.
|
|
func (ctx Context) WithKeyringOptions(opts ...keyring.Option) Context {
|
|
ctx.KeyringOptions = opts
|
|
return ctx
|
|
}
|
|
|
|
// WithInput returns a copy of the context with an updated input.
|
|
func (ctx Context) WithInput(r io.Reader) Context {
|
|
// convert to a bufio.Reader to have a shared buffer between the keyring and the
|
|
// the Commands, ensuring a read from one advance the read pointer for the other.
|
|
// see https://github.com/cosmos/cosmos-sdk/issues/9566.
|
|
ctx.Input = bufio.NewReader(r)
|
|
return ctx
|
|
}
|
|
|
|
// WithCodec returns a copy of the Context with an updated Codec.
|
|
func (ctx Context) WithCodec(m codec.Codec) Context {
|
|
ctx.Codec = m
|
|
return ctx
|
|
}
|
|
|
|
// WithLegacyAmino returns a copy of the context with an updated LegacyAmino codec.
|
|
// TODO: Deprecated (remove).
|
|
func (ctx Context) WithLegacyAmino(cdc *codec.LegacyAmino) Context {
|
|
ctx.LegacyAmino = cdc
|
|
return ctx
|
|
}
|
|
|
|
// WithOutput returns a copy of the context with an updated output writer (e.g. stdout).
|
|
func (ctx Context) WithOutput(w io.Writer) Context {
|
|
ctx.Output = w
|
|
return ctx
|
|
}
|
|
|
|
// WithFrom returns a copy of the context with an updated from address or name.
|
|
func (ctx Context) WithFrom(from string) Context {
|
|
ctx.From = from
|
|
return ctx
|
|
}
|
|
|
|
// WithOutputFormat returns a copy of the context with an updated OutputFormat field.
|
|
func (ctx Context) WithOutputFormat(format string) Context {
|
|
ctx.OutputFormat = format
|
|
return ctx
|
|
}
|
|
|
|
// WithNodeURI returns a copy of the context with an updated node URI.
|
|
func (ctx Context) WithNodeURI(nodeURI string) Context {
|
|
ctx.NodeURI = nodeURI
|
|
return ctx
|
|
}
|
|
|
|
// WithHeight returns a copy of the context with an updated height.
|
|
func (ctx Context) WithHeight(height int64) Context {
|
|
ctx.Height = height
|
|
return ctx
|
|
}
|
|
|
|
// WithClient returns a copy of the context with an updated RPC client
|
|
// instance.
|
|
func (ctx Context) WithClient(client rpcclient.Client) Context {
|
|
ctx.Client = client
|
|
return ctx
|
|
}
|
|
|
|
// WithUseLedger returns a copy of the context with an updated UseLedger flag.
|
|
func (ctx Context) WithUseLedger(useLedger bool) Context {
|
|
ctx.UseLedger = useLedger
|
|
return ctx
|
|
}
|
|
|
|
// WithChainID returns a copy of the context with an updated chain ID.
|
|
func (ctx Context) WithChainID(chainID string) Context {
|
|
ctx.ChainID = chainID
|
|
return ctx
|
|
}
|
|
|
|
// WithHomeDir returns a copy of the Context with HomeDir set.
|
|
func (ctx Context) WithHomeDir(dir string) Context {
|
|
if dir != "" {
|
|
ctx.HomeDir = dir
|
|
}
|
|
return ctx
|
|
}
|
|
|
|
// WithKeyringDir returns a copy of the Context with KeyringDir set.
|
|
func (ctx Context) WithKeyringDir(dir string) Context {
|
|
ctx.KeyringDir = dir
|
|
return ctx
|
|
}
|
|
|
|
// WithGenerateOnly returns a copy of the context with updated GenerateOnly value
|
|
func (ctx Context) WithGenerateOnly(generateOnly bool) Context {
|
|
ctx.GenerateOnly = generateOnly
|
|
return ctx
|
|
}
|
|
|
|
// WithSimulation returns a copy of the context with updated Simulate value
|
|
func (ctx Context) WithSimulation(simulate bool) Context {
|
|
ctx.Simulate = simulate
|
|
return ctx
|
|
}
|
|
|
|
// WithOffline returns a copy of the context with updated Offline value.
|
|
func (ctx Context) WithOffline(offline bool) Context {
|
|
ctx.Offline = offline
|
|
return ctx
|
|
}
|
|
|
|
// WithFromName returns a copy of the context with an updated from account name.
|
|
func (ctx Context) WithFromName(name string) Context {
|
|
ctx.FromName = name
|
|
return ctx
|
|
}
|
|
|
|
// WithFromAddress returns a copy of the context with an updated from account
|
|
// address.
|
|
func (ctx Context) WithFromAddress(addr sdk.AccAddress) Context {
|
|
ctx.FromAddress = addr
|
|
return ctx
|
|
}
|
|
|
|
// WithFeePayerAddress returns a copy of the context with an updated fee payer account
|
|
// address.
|
|
func (ctx Context) WithFeePayerAddress(addr sdk.AccAddress) Context {
|
|
ctx.FeePayer = addr
|
|
return ctx
|
|
}
|
|
|
|
// WithFeeGranterAddress returns a copy of the context with an updated fee granter account
|
|
// address.
|
|
func (ctx Context) WithFeeGranterAddress(addr sdk.AccAddress) Context {
|
|
ctx.FeeGranter = addr
|
|
return ctx
|
|
}
|
|
|
|
// WithBroadcastMode returns a copy of the context with an updated broadcast
|
|
// mode.
|
|
func (ctx Context) WithBroadcastMode(mode string) Context {
|
|
ctx.BroadcastMode = mode
|
|
return ctx
|
|
}
|
|
|
|
// WithSignModeStr returns a copy of the context with an updated SignMode
|
|
// value.
|
|
func (ctx Context) WithSignModeStr(signModeStr string) Context {
|
|
ctx.SignModeStr = signModeStr
|
|
return ctx
|
|
}
|
|
|
|
// WithSkipConfirmation returns a copy of the context with an updated SkipConfirm
|
|
// value.
|
|
func (ctx Context) WithSkipConfirmation(skip bool) Context {
|
|
ctx.SkipConfirm = skip
|
|
return ctx
|
|
}
|
|
|
|
// WithTxConfig returns the context with an updated TxConfig
|
|
func (ctx Context) WithTxConfig(generator TxConfig) Context {
|
|
ctx.TxConfig = generator
|
|
return ctx
|
|
}
|
|
|
|
// WithAccountRetriever returns the context with an updated AccountRetriever
|
|
func (ctx Context) WithAccountRetriever(retriever AccountRetriever) Context {
|
|
ctx.AccountRetriever = retriever
|
|
return ctx
|
|
}
|
|
|
|
// WithInterfaceRegistry returns the context with an updated InterfaceRegistry
|
|
func (ctx Context) WithInterfaceRegistry(interfaceRegistry codectypes.InterfaceRegistry) Context {
|
|
ctx.InterfaceRegistry = interfaceRegistry
|
|
return ctx
|
|
}
|
|
|
|
// WithViper returns the context with Viper field. This Viper instance is used to read
|
|
// client-side config from the config file.
|
|
func (ctx Context) WithViper(prefix string) Context {
|
|
v := viper.New()
|
|
v.SetEnvPrefix(prefix)
|
|
v.AutomaticEnv()
|
|
ctx.Viper = v
|
|
return ctx
|
|
}
|
|
|
|
// WithAux returns a copy of the context with an updated IsAux value.
|
|
func (ctx Context) WithAux(isAux bool) Context {
|
|
ctx.IsAux = isAux
|
|
return ctx
|
|
}
|
|
|
|
// PrintString prints the raw string to ctx.Output if it's defined, otherwise to os.Stdout
|
|
func (ctx Context) PrintString(str string) error {
|
|
return ctx.PrintBytes([]byte(str))
|
|
}
|
|
|
|
// PrintBytes prints the raw bytes to ctx.Output if it's defined, otherwise to os.Stdout.
|
|
// NOTE: for printing a complex state object, you should use ctx.PrintOutput
|
|
func (ctx Context) PrintBytes(o []byte) error {
|
|
writer := ctx.Output
|
|
if writer == nil {
|
|
writer = os.Stdout
|
|
}
|
|
|
|
_, err := writer.Write(o)
|
|
return err
|
|
}
|
|
|
|
// PrintProto outputs toPrint to the ctx.Output based on ctx.OutputFormat which is
|
|
// either text or json. If text, toPrint will be YAML encoded. Otherwise, toPrint
|
|
// will be JSON encoded using ctx.Codec. An error is returned upon failure.
|
|
func (ctx Context) PrintProto(toPrint proto.Message) error {
|
|
// always serialize JSON initially because proto json can't be directly YAML encoded
|
|
out, err := ctx.Codec.MarshalJSON(toPrint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ctx.printOutput(out)
|
|
}
|
|
|
|
// PrintObjectLegacy is a variant of PrintProto that doesn't require a proto.Message type
|
|
// and uses amino JSON encoding.
|
|
// Deprecated: It will be removed in the near future!
|
|
func (ctx Context) PrintObjectLegacy(toPrint interface{}) error {
|
|
out, err := ctx.LegacyAmino.MarshalJSON(toPrint)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return ctx.printOutput(out)
|
|
}
|
|
|
|
func (ctx Context) printOutput(out []byte) error {
|
|
var err error
|
|
if ctx.OutputFormat == "text" {
|
|
out, err = yaml.JSONToYAML(out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
writer := ctx.Output
|
|
if writer == nil {
|
|
writer = os.Stdout
|
|
}
|
|
|
|
_, err = writer.Write(out)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if ctx.OutputFormat != "text" {
|
|
// append new-line for formats besides YAML
|
|
_, err = writer.Write([]byte("\n"))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetFromFields returns a from account address, account name and keyring type, given either
|
|
// an address or key name. If genOnly is true, only a valid Bech32 cosmos
|
|
// address is returned.
|
|
func GetFromFields(kr keyring.Keyring, from string, genOnly bool) (sdk.AccAddress, string, keyring.KeyType, error) {
|
|
if from == "" {
|
|
return nil, "", 0, nil
|
|
}
|
|
|
|
var k *keyring.Record
|
|
if addr, err := sdk.AccAddressFromBech32(from); err == nil {
|
|
k, err = kr.KeyByAddress(addr)
|
|
if err != nil {
|
|
return nil, "", 0, err
|
|
}
|
|
} else {
|
|
k, err = kr.Key(from)
|
|
if err != nil {
|
|
return nil, "", 0, err
|
|
}
|
|
}
|
|
|
|
addr, err := k.GetAddress()
|
|
if err != nil {
|
|
return nil, "", 0, err
|
|
}
|
|
|
|
return addr, k.Name, k.GetType(), nil
|
|
}
|
|
|
|
// NewKeyringFromBackend gets a Keyring object from a backend
|
|
func NewKeyringFromBackend(ctx Context, backend string) (keyring.Keyring, error) {
|
|
if ctx.GenerateOnly || ctx.Simulate {
|
|
backend = keyring.BackendMemory
|
|
}
|
|
|
|
return keyring.New(sdk.KeyringServiceName(), backend, ctx.KeyringDir, ctx.Input, ctx.Codec, ctx.KeyringOptions...)
|
|
}
|