Skip to content

Commit

Permalink
Tx CLI proto module interface (#5989)
Browse files Browse the repository at this point in the history
* WIP

* WIP

* WIP on removing x/auth dependency from client/tx

* Revert unneeded changes

* Simplify cli tx UX

* Wire up bank tx REST routes

* Fix assignment issue

* Wire up bank NewSendTxCmd

* fix lint

* revert file

* revert file

* fix simcli

* Refactor AccountRetriever

* Fix build

* Fix build

* Fix build

* Fix integration tests

* Fix tests

* Docs, linting

* Linting

* WIP on all modules

* Implement other module new tx cmd's

* Fix cmd's

* Refactor existing GetTxCmd

* Fix cmd

* Removing deprecated code

* Update ADR 020 & CHANGELOG

* Lint

* Lint

* Lint

* Lint

* Lint

* Lint

* Lint

* Fix client/tx tests

* Fix mocks

* Fix tests

* Lint fixes

* REST tx migration

* Wire up REST

* Linting

* Update CHANGELOG, docs

* Fix tests

* lint

* Address review feedback

* Update CHANGELOG.md

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>

* Update CHANGELOG.md

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>

* group vars

Co-authored-by: Alexander Bezobchuk <alexanderbez@users.noreply.github.com>
  • Loading branch information
aaronc and alexanderbez authored May 21, 2020
1 parent 970e009 commit 850419f
Show file tree
Hide file tree
Showing 71 changed files with 862 additions and 1,717 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ ALL legacy code should use `*codec.Codec` instead of `*amino.Codec` directly
* (x/gov) [\#6147](https://github.com/cosmos/cosmos-sdk/pull/6147) The `Content` field on `Proposal` and `MsgSubmitProposal`
is now `Any` in concordance with [ADR 019](docs/architecture/adr-019-protobuf-state-encoding.md) and `GetContent` should now
be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposal` constructor now may return an `error`
* (modules) [\#5989](https://github.com/cosmos/cosmos-sdk/pull/5989) `AppModuleBasic.GetTxCmd` now takes a single `CLIContext` parameter.
* (x/auth) [\#5989](https://github.com/cosmos/cosmos-sdk/pull/5989) All `AccountRetriever` methods now take `NodeQuerier` as a parameter instead of as a struct member.

### Features

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ build-sim: go.sum
build-sim

mocks: $(MOCKS_DIR)
mockgen -source=x/auth/types/account_retriever.go -package mocks -destination tests/mocks/account_retriever.go
mockgen -source=client/context/account_retriever.go -package mocks -destination tests/mocks/account_retriever.go
mockgen -package mocks -destination tests/mocks/tendermint_tm_db_DB.go github.com/tendermint/tm-db DB
mockgen -source=types/module/module.go -package mocks -destination tests/mocks/types_module_module.go
mockgen -source=types/invariant.go -package mocks -destination tests/mocks/types_invariant.go
Expand Down
21 changes: 21 additions & 0 deletions client/context/account_retriever.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package context

import "github.com/cosmos/cosmos-sdk/types"

// AccountRetriever defines the interfaces required by transactions to
// ensure an account exists and to be able to query for account fields necessary
// for signing.
type AccountRetriever interface {
EnsureExists(nodeQuerier NodeQuerier, addr types.AccAddress) error
GetAccountNumberSequence(nodeQuerier NodeQuerier, addr types.AccAddress) (accNum uint64, accSeq uint64, err error)
}

// NodeQuerier is an interface that is satisfied by types that provide the QueryWithData method
type NodeQuerier interface {
// QueryWithData performs a query to a Tendermint node with the provided path
// and a data payload. It returns the result and height of the query upon success
// or an error if the query fails.
QueryWithData(path string, data []byte) ([]byte, int64, error)
}

var _ NodeQuerier = CLIContext{}
212 changes: 129 additions & 83 deletions client/context/context.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package context

import (
"bufio"
"fmt"
"io"
"os"

"github.com/pkg/errors"
"github.com/spf13/viper"
yaml "gopkg.in/yaml.v2"

"github.com/tendermint/tendermint/libs/cli"
tmlite "github.com/tendermint/tendermint/lite"
rpcclient "github.com/tendermint/tendermint/rpc/client"
rpchttp "github.com/tendermint/tendermint/rpc/client/http"
yaml "gopkg.in/yaml.v2"

"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
Expand All @@ -22,28 +24,30 @@ import (
// CLIContext implements a typical CLI context created in SDK modules for
// transaction handling and queries.
type CLIContext struct {
FromAddress sdk.AccAddress
Client rpcclient.Client
ChainID string
Marshaler codec.Marshaler
Input io.Reader
Keyring keyring.Keyring
Output io.Writer
OutputFormat string
Height int64
HomeDir string
NodeURI string
From string
BroadcastMode string
Verifier tmlite.Verifier
FromName string
TrustNode bool
UseLedger bool
Simulate bool
GenerateOnly bool
Offline bool
Indent bool
SkipConfirm bool
FromAddress sdk.AccAddress
Client rpcclient.Client
ChainID string
JSONMarshaler codec.JSONMarshaler
Input io.Reader
Keyring keyring.Keyring
Output io.Writer
OutputFormat string
Height int64
HomeDir string
NodeURI string
From string
BroadcastMode string
Verifier tmlite.Verifier
FromName string
TrustNode bool
UseLedger bool
Simulate bool
GenerateOnly bool
Offline bool
Indent bool
SkipConfirm bool
TxGenerator TxGenerator
AccountRetriever AccountRetriever

// TODO: Deprecated (remove).
Codec *codec.Codec
Expand All @@ -56,26 +60,40 @@ type CLIContext struct {
// a CLIContext in tests or any non CLI-based environment, the verifier will not be created
// and will be set as nil because FlagTrustNode must be set.
func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
var nodeURI string
var rpc rpcclient.Client
ctx := CLIContext{}
return ctx.InitWithInputAndFrom(input, from)
}

homedir := viper.GetString(flags.FlagHome)
genOnly := viper.GetBool(flags.FlagGenerateOnly)
backend := viper.GetString(flags.FlagKeyringBackend)
if len(backend) == 0 {
backend = keyring.BackendMemory
}
// NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the
// command line using Viper. It takes a key name or address and populates the FromName and
// FromAddress field accordingly. It will also create Tendermint verifier using
// the chain ID, home directory and RPC URI provided by the command line. If using
// a CLIContext in tests or any non CLI-based environment, the verifier will not
// be created and will be set as nil because FlagTrustNode must be set.
func NewCLIContextWithFrom(from string) CLIContext {
return NewCLIContextWithInputAndFrom(os.Stdin, from)
}

keyring, err := newKeyringFromFlags(backend, homedir, input, genOnly)
if err != nil {
panic(fmt.Errorf("couldn't acquire keyring: %v", err))
}
// NewCLIContext returns a new initialized CLIContext with parameters from the
// command line using Viper.
func NewCLIContext() CLIContext { return NewCLIContextWithFrom(viper.GetString(flags.FlagFrom)) }

fromAddress, fromName, err := GetFromFields(keyring, from, genOnly)
if err != nil {
fmt.Printf("failed to get from fields: %v\n", err)
os.Exit(1)
}
// NewCLIContextWithInput returns a new initialized CLIContext with a io.Reader and parameters
// from the command line using Viper.
func NewCLIContextWithInput(input io.Reader) CLIContext {
return NewCLIContextWithInputAndFrom(input, viper.GetString(flags.FlagFrom))
}

// InitWithInputAndFrom returns a new CLIContext re-initialized from an existing
// CLIContext with a new io.Reader and from parameter
func (ctx CLIContext) InitWithInputAndFrom(input io.Reader, from string) CLIContext {
input = bufio.NewReader(input)

var (
nodeURI string
rpc rpcclient.Client
err error
)

offline := viper.GetBool(flags.FlagOffline)
if !offline {
Expand All @@ -90,29 +108,48 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
}

trustNode := viper.GetBool(flags.FlagTrustNode)
ctx := CLIContext{
Client: rpc,
ChainID: viper.GetString(flags.FlagChainID),
Input: input,
Output: os.Stdout,
NodeURI: nodeURI,
From: viper.GetString(flags.FlagFrom),
Keyring: keyring,
OutputFormat: viper.GetString(cli.OutputFlag),
Height: viper.GetInt64(flags.FlagHeight),
HomeDir: homedir,
TrustNode: trustNode,
UseLedger: viper.GetBool(flags.FlagUseLedger),
BroadcastMode: viper.GetString(flags.FlagBroadcastMode),
Simulate: viper.GetBool(flags.FlagDryRun),
GenerateOnly: genOnly,
Offline: offline,
FromAddress: fromAddress,
FromName: fromName,
Indent: viper.GetBool(flags.FlagIndentResponse),
SkipConfirm: viper.GetBool(flags.FlagSkipConfirmation),

ctx.Client = rpc
ctx.ChainID = viper.GetString(flags.FlagChainID)
ctx.Input = input
ctx.Output = os.Stdout
ctx.NodeURI = nodeURI
ctx.From = viper.GetString(flags.FlagFrom)
ctx.OutputFormat = viper.GetString(cli.OutputFlag)
ctx.Height = viper.GetInt64(flags.FlagHeight)
ctx.TrustNode = trustNode
ctx.UseLedger = viper.GetBool(flags.FlagUseLedger)
ctx.BroadcastMode = viper.GetString(flags.FlagBroadcastMode)
ctx.Simulate = viper.GetBool(flags.FlagDryRun)
ctx.Offline = offline
ctx.Indent = viper.GetBool(flags.FlagIndentResponse)
ctx.SkipConfirm = viper.GetBool(flags.FlagSkipConfirmation)

homedir := viper.GetString(flags.FlagHome)
genOnly := viper.GetBool(flags.FlagGenerateOnly)
backend := viper.GetString(flags.FlagKeyringBackend)
if len(backend) == 0 {
backend = keyring.BackendMemory
}

kr, err := newKeyringFromFlags(backend, homedir, input, genOnly)
if err != nil {
panic(fmt.Errorf("couldn't acquire keyring: %v", err))
}

fromAddress, fromName, err := GetFromFields(kr, from, genOnly)
if err != nil {
fmt.Printf("failed to get from fields: %v\n", err)
os.Exit(1)
}

ctx.HomeDir = homedir

ctx.Keyring = kr
ctx.FromAddress = fromAddress
ctx.FromName = fromName
ctx.GenerateOnly = genOnly

if offline {
return ctx
}
Expand All @@ -124,27 +161,24 @@ func NewCLIContextWithInputAndFrom(input io.Reader, from string) CLIContext {
os.Exit(1)
}

return ctx.WithVerifier(verifier)
ctx.Verifier = verifier
return ctx
}

// NewCLIContextWithFrom returns a new initialized CLIContext with parameters from the
// command line using Viper. It takes a key name or address and populates the FromName and
// FromAddress field accordingly. It will also create Tendermint verifier using
// the chain ID, home directory and RPC URI provided by the command line. If using
// a CLIContext in tests or any non CLI-based environment, the verifier will not
// be created and will be set as nil because FlagTrustNode must be set.
func NewCLIContextWithFrom(from string) CLIContext {
return NewCLIContextWithInputAndFrom(os.Stdin, from)
// InitWithFrom returns a new CLIContext re-initialized from an existing
// CLIContext with a new from parameter
func (ctx CLIContext) InitWithFrom(from string) CLIContext {
return ctx.InitWithInputAndFrom(os.Stdin, from)
}

// NewCLIContext returns a new initialized CLIContext with parameters from the
// command line using Viper.
func NewCLIContext() CLIContext { return NewCLIContextWithFrom(viper.GetString(flags.FlagFrom)) }
// Init returns a new CLIContext re-initialized from an existing
// CLIContext with parameters from the command line using Viper.
func (ctx CLIContext) Init() CLIContext { return ctx.InitWithFrom(viper.GetString(flags.FlagFrom)) }

// NewCLIContextWithInput returns a new initialized CLIContext with a io.Reader and parameters
// from the command line using Viper.
func NewCLIContextWithInput(input io.Reader) CLIContext {
return NewCLIContextWithInputAndFrom(input, viper.GetString(flags.FlagFrom))
// InitWithInput returns a new CLIContext re-initialized from an existing
// CLIContext with a new io.Reader and from parameter
func (ctx CLIContext) InitWithInput(input io.Reader) CLIContext {
return ctx.InitWithInputAndFrom(input, viper.GetString(flags.FlagFrom))
}

// WithKeyring returns a copy of the context with an updated keyring.
Expand All @@ -159,9 +193,9 @@ func (ctx CLIContext) WithInput(r io.Reader) CLIContext {
return ctx
}

// WithMarshaler returns a copy of the CLIContext with an updated Marshaler.
func (ctx CLIContext) WithMarshaler(m codec.Marshaler) CLIContext {
ctx.Marshaler = m
// WithJSONMarshaler returns a copy of the CLIContext with an updated JSONMarshaler.
func (ctx CLIContext) WithJSONMarshaler(m codec.JSONMarshaler) CLIContext {
ctx.JSONMarshaler = m
return ctx
}

Expand Down Expand Up @@ -265,9 +299,21 @@ func (ctx CLIContext) WithBroadcastMode(mode string) CLIContext {
return ctx
}

// WithTxGenerator returns the context with an updated TxGenerator
func (ctx CLIContext) WithTxGenerator(generator TxGenerator) CLIContext {
ctx.TxGenerator = generator
return ctx
}

// WithAccountRetriever returns the context with an updated AccountRetriever
func (ctx CLIContext) WithAccountRetriever(retriever AccountRetriever) CLIContext {
ctx.AccountRetriever = retriever
return ctx
}

// Println 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.Marshaler. An error is returned upon failure.
// will be JSON encoded using ctx.JSONMarshaler. An error is returned upon failure.
func (ctx CLIContext) Println(toPrint interface{}) error {
var (
out []byte
Expand All @@ -279,11 +325,11 @@ func (ctx CLIContext) Println(toPrint interface{}) error {
out, err = yaml.Marshal(&toPrint)

case "json":
out, err = ctx.Marshaler.MarshalJSON(toPrint)
out, err = ctx.JSONMarshaler.MarshalJSON(toPrint)

// To JSON indent, we re-encode the already encoded JSON given there is no
// error. The re-encoded JSON uses the standard library as the initial encoded
// JSON should have the correct output produced by ctx.Marshaler.
// JSON should have the correct output produced by ctx.JSONMarshaler.
if ctx.Indent && err == nil {
out, err = codec.MarshalIndentFromJSON(out)
}
Expand Down
51 changes: 51 additions & 0 deletions client/context/tx_generator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package context

import (
"github.com/tendermint/tendermint/crypto"

"github.com/cosmos/cosmos-sdk/types"
)

type (
// TxGenerator defines an interface a client can utilize to generate an
// application-defined concrete transaction type. The type returned must
// implement TxBuilder.
TxGenerator interface {
NewTx() TxBuilder
NewFee() ClientFee
NewSignature() ClientSignature
MarshalTx(tx types.Tx) ([]byte, error)
}

ClientFee interface {
types.Fee
SetGas(uint64)
SetAmount(types.Coins)
}

ClientSignature interface {
types.Signature
SetPubKey(crypto.PubKey) error
SetSignature([]byte)
}

// TxBuilder defines an interface which an application-defined concrete transaction
// type must implement. Namely, it must be able to set messages, generate
// signatures, and provide canonical bytes to sign over. The transaction must
// also know how to encode itself.
TxBuilder interface {
GetTx() types.Tx

SetMsgs(...types.Msg) error
GetSignatures() []types.Signature
SetSignatures(...ClientSignature) error
GetFee() types.Fee
SetFee(ClientFee) error
GetMemo() string
SetMemo(string)

// CanonicalSignBytes returns the canonical sign bytes to sign over, given a
// chain ID, along with an account and sequence number.
CanonicalSignBytes(cid string, num, seq uint64) ([]byte, error)
}
)
Loading

0 comments on commit 850419f

Please sign in to comment.