Merge PR #1741: CoreContext Refactor
This commit is contained in:
committed by
Christopher Goes
parent
3c4985c315
commit
12c2c236c2
@@ -0,0 +1,115 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
)
|
||||
|
||||
const ctxAccStoreName = "acc"
|
||||
|
||||
// CLIContext implements a typical CLI context created in SDK modules for
|
||||
// transaction handling and queries.
|
||||
type CLIContext struct {
|
||||
Codec *wire.Codec
|
||||
AccDecoder auth.AccountDecoder
|
||||
Client rpcclient.Client
|
||||
Logger io.Writer
|
||||
Height int64
|
||||
NodeURI string
|
||||
FromAddressName string
|
||||
AccountStore string
|
||||
TrustNode bool
|
||||
UseLedger bool
|
||||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
}
|
||||
|
||||
// NewCLIContext returns a new initialized CLIContext with parameters from the
|
||||
// command line using Viper.
|
||||
func NewCLIContext() CLIContext {
|
||||
var rpc rpcclient.Client
|
||||
|
||||
nodeURI := viper.GetString(client.FlagNode)
|
||||
if nodeURI != "" {
|
||||
rpc = rpcclient.NewHTTP(nodeURI, "/websocket")
|
||||
}
|
||||
|
||||
return CLIContext{
|
||||
Client: rpc,
|
||||
NodeURI: nodeURI,
|
||||
AccountStore: ctxAccStoreName,
|
||||
FromAddressName: viper.GetString(client.FlagFrom),
|
||||
Height: viper.GetInt64(client.FlagHeight),
|
||||
TrustNode: viper.GetBool(client.FlagTrustNode),
|
||||
UseLedger: viper.GetBool(client.FlagUseLedger),
|
||||
Async: viper.GetBool(client.FlagAsync),
|
||||
JSON: viper.GetBool(client.FlagJson),
|
||||
PrintResponse: viper.GetBool(client.FlagPrintResponse),
|
||||
}
|
||||
}
|
||||
|
||||
// WithCodec returns a copy of the context with an updated codec.
|
||||
func (ctx CLIContext) WithCodec(cdc *wire.Codec) CLIContext {
|
||||
ctx.Codec = cdc
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithAccountDecoder returns a copy of the context with an updated account
|
||||
// decoder.
|
||||
func (ctx CLIContext) WithAccountDecoder(decoder auth.AccountDecoder) CLIContext {
|
||||
ctx.AccDecoder = decoder
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithLogger returns a copy of the context with an updated logger.
|
||||
func (ctx CLIContext) WithLogger(w io.Writer) CLIContext {
|
||||
ctx.Logger = w
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithAccountStore returns a copy of the context with an updated AccountStore.
|
||||
func (ctx CLIContext) WithAccountStore(accountStore string) CLIContext {
|
||||
ctx.AccountStore = accountStore
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithFromAddressName returns a copy of the context with an updated from
|
||||
// address.
|
||||
func (ctx CLIContext) WithFromAddressName(addrName string) CLIContext {
|
||||
ctx.FromAddressName = addrName
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithTrustNode returns a copy of the context with an updated TrustNode flag.
|
||||
func (ctx CLIContext) WithTrustNode(trustNode bool) CLIContext {
|
||||
ctx.TrustNode = trustNode
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithNodeURI returns a copy of the context with an updated node URI.
|
||||
func (ctx CLIContext) WithNodeURI(nodeURI string) CLIContext {
|
||||
ctx.NodeURI = nodeURI
|
||||
ctx.Client = rpcclient.NewHTTP(nodeURI, "/websocket")
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithClient returns a copy of the context with an updated RPC client
|
||||
// instance.
|
||||
func (ctx CLIContext) WithClient(client rpcclient.Client) CLIContext {
|
||||
ctx.Client = client
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithUseLedger returns a copy of the context with an updated UseLedger flag.
|
||||
func (ctx CLIContext) WithUseLedger(useLedger bool) CLIContext {
|
||||
ctx.UseLedger = useLedger
|
||||
return ctx
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ErrInvalidAccount returns a standardized error reflecting that a given
|
||||
// account address does not exist.
|
||||
func ErrInvalidAccount(addr sdk.AccAddress) error {
|
||||
return errors.Errorf(`No account with address %s was found in the state.
|
||||
Are you sure there has been a transaction involving it?`, addr)
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// Broadcast the transaction bytes to Tendermint
|
||||
func (ctx CoreContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if res.CheckTx.Code != uint32(0) {
|
||||
return res, errors.Errorf("checkTx failed: (%d) %s",
|
||||
res.CheckTx.Code,
|
||||
res.CheckTx.Log)
|
||||
}
|
||||
if res.DeliverTx.Code != uint32(0) {
|
||||
return res, errors.Errorf("deliverTx failed: (%d) %s",
|
||||
res.DeliverTx.Code,
|
||||
res.DeliverTx.Log)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Broadcast the transaction bytes to Tendermint
|
||||
func (ctx CoreContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxAsync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Query information about the connected node
|
||||
func (ctx CoreContext) Query(path string) (res []byte, err error) {
|
||||
return ctx.query(path, nil)
|
||||
}
|
||||
|
||||
// QueryStore from Tendermint with the provided key and storename
|
||||
func (ctx CoreContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) {
|
||||
return ctx.queryStore(key, storeName, "key")
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and subspace
|
||||
func (ctx CoreContext) QuerySubspace(cdc *wire.Codec, subspace []byte, storeName string) (res []sdk.KVPair, err error) {
|
||||
resRaw, err := ctx.queryStore(subspace, storeName, "subspace")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
cdc.MustUnmarshalBinary(resRaw, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and path
|
||||
func (ctx CoreContext) query(path string, key common.HexBytes) (res []byte, err error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
opts := rpcclient.ABCIQueryOptions{
|
||||
Height: ctx.Height,
|
||||
Trusted: ctx.TrustNode,
|
||||
}
|
||||
result, err := node.ABCIQueryWithOptions(path, key, opts)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
resp := result.Response
|
||||
if resp.Code != uint32(0) {
|
||||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
|
||||
}
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// Query from Tendermint with the provided storename and path
|
||||
func (ctx CoreContext) queryStore(key cmn.HexBytes, storeName, endPath string) (res []byte, err error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
return ctx.query(path, key)
|
||||
}
|
||||
|
||||
// Get the from address from the name flag
|
||||
func (ctx CoreContext) GetFromAddress() (from sdk.AccAddress, err error) {
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
name := ctx.FromAddressName
|
||||
if name == "" {
|
||||
return nil, errors.Errorf("must provide a from address name")
|
||||
}
|
||||
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("no key for: %s", name)
|
||||
}
|
||||
|
||||
return sdk.AccAddress(info.GetPubKey().Address()), nil
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) SignAndBuild(name, passphrase string, msgs []sdk.Msg, cdc *wire.Codec) ([]byte, error) {
|
||||
|
||||
// build the Sign Messsage from the Standard Message
|
||||
chainID := ctx.ChainID
|
||||
if chainID == "" {
|
||||
return nil, errors.Errorf("chain ID required but not specified")
|
||||
}
|
||||
accnum := ctx.AccountNumber
|
||||
sequence := ctx.Sequence
|
||||
memo := ctx.Memo
|
||||
|
||||
fee := sdk.Coin{}
|
||||
if ctx.Fee != "" {
|
||||
parsedFee, err := sdk.ParseCoin(ctx.Fee)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fee = parsedFee
|
||||
}
|
||||
|
||||
signMsg := auth.StdSignMsg{
|
||||
ChainID: chainID,
|
||||
AccountNumber: accnum,
|
||||
Sequence: sequence,
|
||||
Msgs: msgs,
|
||||
Memo: memo,
|
||||
Fee: auth.NewStdFee(ctx.Gas, fee), // TODO run simulate to estimate gas?
|
||||
}
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// sign and build
|
||||
bz := signMsg.Bytes()
|
||||
|
||||
sig, pubkey, err := keybase.Sign(name, passphrase, bz)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sigs := []auth.StdSignature{{
|
||||
PubKey: pubkey,
|
||||
Signature: sig,
|
||||
AccountNumber: accnum,
|
||||
Sequence: sequence,
|
||||
}}
|
||||
|
||||
// marshal bytes
|
||||
tx := auth.NewStdTx(signMsg.Msgs, signMsg.Fee, sigs, memo)
|
||||
|
||||
return cdc.MarshalBinary(tx)
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) ensureSignBuild(name string, msgs []sdk.Msg, cdc *wire.Codec) (tyBytes []byte, err error) {
|
||||
err = EnsureAccountExists(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, err = EnsureAccountNumber(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// default to next sequence number if none provided
|
||||
ctx, err = EnsureSequence(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var txBytes []byte
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var passphrase string
|
||||
// Only need a passphrase for locally-stored keys
|
||||
if info.GetType() == "local" {
|
||||
passphrase, err = ctx.GetPassphraseFromStdin(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error fetching passphrase: %v", err)
|
||||
}
|
||||
}
|
||||
txBytes, err = ctx.SignAndBuild(name, passphrase, msgs, cdc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error signing transaction: %v", err)
|
||||
}
|
||||
|
||||
return txBytes, err
|
||||
}
|
||||
|
||||
// sign and build the transaction from the msg
|
||||
func (ctx CoreContext) EnsureSignBuildBroadcast(name string, msgs []sdk.Msg, cdc *wire.Codec) (err error) {
|
||||
|
||||
txBytes, err := ctx.ensureSignBuild(name, msgs, cdc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.Async {
|
||||
res, err := ctx.BroadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.JSON {
|
||||
type toJSON struct {
|
||||
TxHash string
|
||||
}
|
||||
valueToJSON := toJSON{res.Hash.String()}
|
||||
JSON, err := cdc.MarshalJSON(valueToJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(JSON))
|
||||
} else {
|
||||
fmt.Println("Async tx sent. tx hash: ", res.Hash.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
res, err := ctx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if ctx.JSON {
|
||||
// Since JSON is intended for automated scripts, always include response in JSON mode
|
||||
type toJSON struct {
|
||||
Height int64
|
||||
TxHash string
|
||||
Response string
|
||||
}
|
||||
valueToJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)}
|
||||
JSON, err := cdc.MarshalJSON(valueToJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(JSON))
|
||||
return nil
|
||||
}
|
||||
if ctx.PrintResponse {
|
||||
fmt.Printf("Committed at block %d. Hash: %s Response:%+v \n", res.Height, res.Hash.String(), res.DeliverTx)
|
||||
} else {
|
||||
fmt.Printf("Committed at block %d. Hash: %s \n", res.Height, res.Hash.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// get the next sequence for the account address
|
||||
func (ctx CoreContext) GetAccountNumber(address []byte) (int64, error) {
|
||||
if ctx.Decoder == nil {
|
||||
return 0, errors.New("accountDecoder required but not provided")
|
||||
}
|
||||
|
||||
res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
fmt.Printf("No account found. Returning 0.\n")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
account, err := ctx.Decoder(res)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return account.GetAccountNumber(), nil
|
||||
}
|
||||
|
||||
// get the next sequence for the account address
|
||||
func (ctx CoreContext) NextSequence(address []byte) (int64, error) {
|
||||
if ctx.Decoder == nil {
|
||||
return 0, errors.New("accountDecoder required but not provided")
|
||||
}
|
||||
|
||||
res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
fmt.Printf("No account found, defaulting to sequence 0\n")
|
||||
return 0, err
|
||||
}
|
||||
|
||||
account, err := ctx.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()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
return client.GetPassword(prompt, buf)
|
||||
}
|
||||
|
||||
// GetNode prepares a simple rpc.Client
|
||||
func (ctx CoreContext) GetNode() (rpcclient.Client, error) {
|
||||
if ctx.Client == nil {
|
||||
return nil, errors.New("must define node URI")
|
||||
}
|
||||
return ctx.Client, nil
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/tendermint/tendermint/libs/common"
|
||||
cmn "github.com/tendermint/tendermint/libs/common"
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
// GetNode returns an RPC client. If the context's client is not defined, an
|
||||
// error is returned.
|
||||
func (ctx CLIContext) GetNode() (rpcclient.Client, error) {
|
||||
if ctx.Client == nil {
|
||||
return nil, errors.New("no RPC client defined")
|
||||
}
|
||||
|
||||
return ctx.Client, nil
|
||||
}
|
||||
|
||||
// Query performs a query for information about the connected node.
|
||||
func (ctx CLIContext) Query(path string) (res []byte, err error) {
|
||||
return ctx.query(path, nil)
|
||||
}
|
||||
|
||||
// QueryStore performs a query from a Tendermint node with the provided key and
|
||||
// store name.
|
||||
func (ctx CLIContext) QueryStore(key cmn.HexBytes, storeName string) (res []byte, err error) {
|
||||
return ctx.queryStore(key, storeName, "key")
|
||||
}
|
||||
|
||||
// QuerySubspace performs a query from a Tendermint node with the provided
|
||||
// store name and subspace.
|
||||
func (ctx CLIContext) QuerySubspace(subspace []byte, storeName string) (res []sdk.KVPair, err error) {
|
||||
resRaw, err := ctx.queryStore(subspace, storeName, "subspace")
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
ctx.Codec.MustUnmarshalBinary(resRaw, &res)
|
||||
return
|
||||
}
|
||||
|
||||
// GetAccount queries for an account given an address and a block height. An
|
||||
// error is returned if the query or decoding fails.
|
||||
func (ctx CLIContext) GetAccount(address []byte) (auth.Account, error) {
|
||||
if ctx.AccDecoder == nil {
|
||||
return nil, errors.New("account decoder required but not provided")
|
||||
}
|
||||
|
||||
res, err := ctx.QueryStore(auth.AddressStoreKey(address), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if len(res) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
account, err := ctx.AccDecoder(res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// GetFromAddress returns the from address from the context's name.
|
||||
func (ctx CLIContext) GetFromAddress() (from sdk.AccAddress, err error) {
|
||||
if ctx.FromAddressName == "" {
|
||||
return nil, errors.Errorf("must provide a from address name")
|
||||
}
|
||||
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := keybase.Get(ctx.FromAddressName)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("no key for: %s", ctx.FromAddressName)
|
||||
}
|
||||
|
||||
return sdk.AccAddress(info.GetPubKey().Address()), nil
|
||||
}
|
||||
|
||||
// GetAccountNumber returns the next account number for the given account
|
||||
// address.
|
||||
func (ctx CLIContext) GetAccountNumber(address []byte) (int64, error) {
|
||||
account, err := ctx.GetAccount(address)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return account.GetAccountNumber(), nil
|
||||
}
|
||||
|
||||
// GetAccountSequence returns the sequence number for the given account
|
||||
// address.
|
||||
func (ctx CLIContext) GetAccountSequence(address []byte) (int64, error) {
|
||||
account, err := ctx.GetAccount(address)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return account.GetSequence(), nil
|
||||
}
|
||||
|
||||
// BroadcastTx broadcasts transaction bytes to a Tendermint node.
|
||||
func (ctx CLIContext) BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxCommit(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if !res.CheckTx.IsOK() {
|
||||
return res, errors.Errorf("checkTx failed: (%d) %s",
|
||||
res.CheckTx.Code,
|
||||
res.CheckTx.Log)
|
||||
}
|
||||
|
||||
if !res.DeliverTx.IsOK() {
|
||||
return res, errors.Errorf("deliverTx failed: (%d) %s",
|
||||
res.DeliverTx.Code,
|
||||
res.DeliverTx.Log)
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// BroadcastTxAsync broadcasts transaction bytes to a Tendermint node
|
||||
// asynchronously.
|
||||
func (ctx CLIContext) BroadcastTxAsync(tx []byte) (*ctypes.ResultBroadcastTx, error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := node.BroadcastTxAsync(tx)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
return res, err
|
||||
}
|
||||
|
||||
// EnsureAccountExists ensures that an account exists for a given context. An
|
||||
// error is returned if it does not.
|
||||
func (ctx CLIContext) EnsureAccountExists() error {
|
||||
addr, err := ctx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(accountBytes) == 0 {
|
||||
return ErrInvalidAccount(addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureAccountExistsFromAddr ensures that an account exists for a given
|
||||
// address. Instead of using the context's from name, a direct address is
|
||||
// given. An error is returned if it does not.
|
||||
func (ctx CLIContext) EnsureAccountExistsFromAddr(addr sdk.AccAddress) error {
|
||||
accountBytes, err := ctx.QueryStore(auth.AddressStoreKey(addr), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(accountBytes) == 0 {
|
||||
return ErrInvalidAccount(addr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureBroadcastTx broadcasts a transactions either synchronously or
|
||||
// asynchronously based on the context parameters. The result of the broadcast
|
||||
// is parsed into an intermediate structure which is logged if the context has
|
||||
// a logger defined.
|
||||
func (ctx CLIContext) EnsureBroadcastTx(txBytes []byte) error {
|
||||
if ctx.Async {
|
||||
return ctx.ensureBroadcastTxAsync(txBytes)
|
||||
}
|
||||
|
||||
return ctx.ensureBroadcastTx(txBytes)
|
||||
}
|
||||
|
||||
func (ctx CLIContext) ensureBroadcastTxAsync(txBytes []byte) error {
|
||||
res, err := ctx.BroadcastTxAsync(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.JSON {
|
||||
type toJSON struct {
|
||||
TxHash string
|
||||
}
|
||||
|
||||
if ctx.Logger != nil {
|
||||
resJSON := toJSON{res.Hash.String()}
|
||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger.Write(bz)
|
||||
io.WriteString(ctx.Logger, "\n")
|
||||
}
|
||||
} else {
|
||||
if ctx.Logger != nil {
|
||||
io.WriteString(ctx.Logger, fmt.Sprintf("Async tx sent (tx hash: %s)\n", res.Hash))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx CLIContext) ensureBroadcastTx(txBytes []byte) error {
|
||||
res, err := ctx.BroadcastTx(txBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ctx.JSON {
|
||||
// since JSON is intended for automated scripts, always include
|
||||
// response in JSON mode.
|
||||
type toJSON struct {
|
||||
Height int64
|
||||
TxHash string
|
||||
Response string
|
||||
}
|
||||
|
||||
if ctx.Logger != nil {
|
||||
resJSON := toJSON{res.Height, res.Hash.String(), fmt.Sprintf("%+v", res.DeliverTx)}
|
||||
bz, err := ctx.Codec.MarshalJSON(resJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx.Logger.Write(bz)
|
||||
io.WriteString(ctx.Logger, "\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if ctx.Logger != nil {
|
||||
resStr := fmt.Sprintf("Committed at block %d (tx hash: %s)\n", res.Height, res.Hash.String())
|
||||
|
||||
if ctx.PrintResponse {
|
||||
resStr = fmt.Sprintf("Committed at block %d (tx hash: %s, response: %+v)\n",
|
||||
res.Height, res.Hash.String(), res.DeliverTx,
|
||||
)
|
||||
}
|
||||
|
||||
io.WriteString(ctx.Logger, resStr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// query performs a query from a Tendermint node with the provided store name
|
||||
// and path.
|
||||
func (ctx CLIContext) query(path string, key common.HexBytes) (res []byte, err error) {
|
||||
node, err := ctx.GetNode()
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
opts := rpcclient.ABCIQueryOptions{
|
||||
Height: ctx.Height,
|
||||
Trusted: ctx.TrustNode,
|
||||
}
|
||||
|
||||
result, err := node.ABCIQueryWithOptions(path, key, opts)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
resp := result.Response
|
||||
if !resp.IsOK() {
|
||||
return res, errors.Errorf("query failed: (%d) %s", resp.Code, resp.Log)
|
||||
}
|
||||
|
||||
return resp.Value, nil
|
||||
}
|
||||
|
||||
// queryStore performs a query from a Tendermint node with the provided a store
|
||||
// name and path.
|
||||
func (ctx CLIContext) queryStore(key cmn.HexBytes, storeName, endPath string) ([]byte, error) {
|
||||
path := fmt.Sprintf("/store/%s/%s", storeName, endPath)
|
||||
return ctx.query(path, key)
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
rpcclient "github.com/tendermint/tendermint/rpc/client"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// typical context created in sdk modules for transactions/queries
|
||||
type CoreContext struct {
|
||||
ChainID string
|
||||
Height int64
|
||||
Gas int64
|
||||
Fee string
|
||||
TrustNode bool
|
||||
NodeURI string
|
||||
FromAddressName string
|
||||
AccountNumber int64
|
||||
Sequence int64
|
||||
Memo string
|
||||
Client rpcclient.Client
|
||||
Decoder auth.AccountDecoder
|
||||
AccountStore string
|
||||
UseLedger bool
|
||||
Async bool
|
||||
JSON bool
|
||||
PrintResponse bool
|
||||
}
|
||||
|
||||
// WithChainID - return a copy of the context with an updated chainID
|
||||
func (c CoreContext) WithChainID(chainID string) CoreContext {
|
||||
c.ChainID = chainID
|
||||
return c
|
||||
}
|
||||
|
||||
// WithHeight - return a copy of the context with an updated height
|
||||
func (c CoreContext) WithHeight(height int64) CoreContext {
|
||||
c.Height = height
|
||||
return c
|
||||
}
|
||||
|
||||
// WithGas - return a copy of the context with an updated gas
|
||||
func (c CoreContext) WithGas(gas int64) CoreContext {
|
||||
c.Gas = gas
|
||||
return c
|
||||
}
|
||||
|
||||
// WithFee - return a copy of the context with an updated fee
|
||||
func (c CoreContext) WithFee(fee string) CoreContext {
|
||||
c.Fee = fee
|
||||
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 account number
|
||||
func (c CoreContext) WithAccountNumber(accnum int64) CoreContext {
|
||||
c.AccountNumber = accnum
|
||||
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
|
||||
}
|
||||
|
||||
// WithMemo - return a copy of the context with an updated memo
|
||||
func (c CoreContext) WithMemo(memo string) CoreContext {
|
||||
c.Memo = memo
|
||||
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 auth.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
|
||||
}
|
||||
|
||||
// WithUseLedger - return a copy of the context with an updated UseLedger
|
||||
func (c CoreContext) WithUseLedger(useLedger bool) CoreContext {
|
||||
c.UseLedger = useLedger
|
||||
return c
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
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/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// NewCoreContextFromViper - return a new context with parameters from the command line
|
||||
func NewCoreContextFromViper() 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
|
||||
}
|
||||
}
|
||||
// TODO: Remove the following deprecation code after Gaia-7000 is launched
|
||||
keyName := viper.GetString(client.FlagName)
|
||||
if keyName != "" {
|
||||
fmt.Println("** Note --name is deprecated and will be removed next release. Please use --from instead **")
|
||||
} else {
|
||||
keyName = viper.GetString(client.FlagFrom)
|
||||
}
|
||||
return CoreContext{
|
||||
ChainID: chainID,
|
||||
Height: viper.GetInt64(client.FlagHeight),
|
||||
Gas: viper.GetInt64(client.FlagGas),
|
||||
Fee: viper.GetString(client.FlagFee),
|
||||
TrustNode: viper.GetBool(client.FlagTrustNode),
|
||||
FromAddressName: keyName,
|
||||
NodeURI: nodeURI,
|
||||
AccountNumber: viper.GetInt64(client.FlagAccountNumber),
|
||||
Sequence: viper.GetInt64(client.FlagSequence),
|
||||
Memo: viper.GetString(client.FlagMemo),
|
||||
Client: rpc,
|
||||
Decoder: nil,
|
||||
AccountStore: "acc",
|
||||
UseLedger: viper.GetBool(client.FlagUseLedger),
|
||||
Async: viper.GetBool(client.FlagAsync),
|
||||
JSON: viper.GetBool(client.FlagJson),
|
||||
PrintResponse: viper.GetBool(client.FlagPrintResponse),
|
||||
}
|
||||
}
|
||||
|
||||
// read chain ID from genesis file, if present
|
||||
func defaultChainID() (string, error) {
|
||||
cfg, err := tcmd.ParseConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
doc, err := tmtypes.GenesisDocFromFile(cfg.GenesisFile())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return doc.ChainID, nil
|
||||
}
|
||||
|
||||
// EnsureAccountExists - Make sure account exists
|
||||
func EnsureAccountExists(ctx CoreContext, name string) error {
|
||||
keybase, err := keys.GetKeyBase()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if name == "" {
|
||||
return errors.Errorf("must provide a from address name")
|
||||
}
|
||||
|
||||
info, err := keybase.Get(name)
|
||||
if err != nil {
|
||||
return errors.Errorf("no key for: %s", name)
|
||||
}
|
||||
|
||||
accAddr := sdk.AccAddress(info.GetPubKey().Address())
|
||||
|
||||
Acc, err := ctx.QueryStore(auth.AddressStoreKey(accAddr), ctx.AccountStore)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if account was found
|
||||
if Acc == nil {
|
||||
return errors.Errorf("No account with address %s was found in the state.\nAre you sure there has been a transaction involving it?", accAddr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EnsureAccount - automatically set account number if none provided
|
||||
func EnsureAccountNumber(ctx CoreContext) (CoreContext, error) {
|
||||
// Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331
|
||||
if viper.GetInt64(client.FlagAccountNumber) != 0 {
|
||||
return ctx, nil
|
||||
}
|
||||
from, err := ctx.GetFromAddress()
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
accnum, err := ctx.GetAccountNumber(from)
|
||||
if err != nil {
|
||||
return ctx, err
|
||||
}
|
||||
fmt.Printf("Defaulting to account number: %d\n", accnum)
|
||||
ctx = ctx.WithAccountNumber(accnum)
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// EnsureSequence - automatically set sequence number if none provided
|
||||
func EnsureSequence(ctx CoreContext) (CoreContext, error) {
|
||||
// Should be viper.IsSet, but this does not work - https://github.com/spf13/viper/pull/331
|
||||
if viper.GetInt64(client.FlagSequence) != 0 {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user