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
|
||||
}
|
||||
@@ -42,7 +42,6 @@ func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
|
||||
for _, c := range cmds {
|
||||
c.Flags().String(FlagFrom, "", "Name of private key with which to sign")
|
||||
c.Flags().String(FlagName, "", "DEPRECATED - Name of private key with which to sign")
|
||||
c.Flags().Int64(FlagAccountNumber, 0, "AccountNumber number to sign the tx")
|
||||
c.Flags().Int64(FlagSequence, 0, "Sequence number to sign the tx")
|
||||
c.Flags().String(FlagMemo, "", "Memo to send along with transaction")
|
||||
|
||||
+50
-1
@@ -29,6 +29,55 @@ func GetKeyBase() (keys.Keybase, error) {
|
||||
return GetKeyBaseFromDir(rootDir)
|
||||
}
|
||||
|
||||
// GetKeyInfo returns key info for a given name. An error is returned if the
|
||||
// keybase cannot be retrieved or getting the info fails.
|
||||
func GetKeyInfo(name string) (keys.Info, error) {
|
||||
keybase, err := GetKeyBase()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keybase.Get(name)
|
||||
}
|
||||
|
||||
// GetPassphrase returns a passphrase for a given name. It will first retrieve
|
||||
// the key info for that name if the type is local, it'll fetch input from
|
||||
// STDIN. Otherwise, an empty passphrase is returned. An error is returned if
|
||||
// the key info cannot be fetched or reading from STDIN fails.
|
||||
func GetPassphrase(name string) (string, error) {
|
||||
var passphrase string
|
||||
|
||||
keyInfo, err := GetKeyInfo(name)
|
||||
if err != nil {
|
||||
return passphrase, err
|
||||
}
|
||||
|
||||
// we only need a passphrase for locally stored keys
|
||||
// TODO: (ref: #864) address security concerns
|
||||
if keyInfo.GetType() == keys.TypeLocal {
|
||||
passphrase, err = ReadPassphraseFromStdin(name)
|
||||
if err != nil {
|
||||
return passphrase, err
|
||||
}
|
||||
}
|
||||
|
||||
return passphrase, nil
|
||||
}
|
||||
|
||||
// ReadPassphraseFromStdin attempts to read a passphrase from STDIN return an
|
||||
// error upon failure.
|
||||
func ReadPassphraseFromStdin(name string) (string, error) {
|
||||
buf := client.BufferStdin()
|
||||
prompt := fmt.Sprintf("Password to sign with '%s':", name)
|
||||
|
||||
passphrase, err := client.GetPassword(prompt, buf)
|
||||
if err != nil {
|
||||
return passphrase, fmt.Errorf("Error reading passphrase: %v", err)
|
||||
}
|
||||
|
||||
return passphrase, nil
|
||||
}
|
||||
|
||||
// initialize a keybase based on the configuration
|
||||
func GetKeyBaseFromDir(rootDir string) (keys.Keybase, error) {
|
||||
if keybase == nil {
|
||||
@@ -77,7 +126,7 @@ func Bech32KeyOutput(info keys.Info) (KeyOutput, error) {
|
||||
}
|
||||
return KeyOutput{
|
||||
Name: info.GetName(),
|
||||
Type: info.GetType(),
|
||||
Type: info.GetType().String(),
|
||||
Address: account,
|
||||
PubKey: bechPubKey,
|
||||
}, nil
|
||||
|
||||
@@ -647,7 +647,6 @@ func doSend(t *testing.T, port, seed, name, password string, addr sdk.AccAddress
|
||||
accnum := acc.GetAccountNumber()
|
||||
sequence := acc.GetSequence()
|
||||
chainID := viper.GetString(client.FlagChainID)
|
||||
|
||||
// send
|
||||
coinbz, err := cdc.MarshalJSON(sdk.NewInt64Coin("steak", 1))
|
||||
if err != nil {
|
||||
@@ -693,7 +692,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc
|
||||
"account_number":"%d",
|
||||
"sequence": "%d",
|
||||
"gas": "100000",
|
||||
"chain_id": "%s",
|
||||
"src_chain_id": "%s",
|
||||
"amount":[
|
||||
{
|
||||
"denom": "%s",
|
||||
@@ -701,6 +700,7 @@ func doIBCTransfer(t *testing.T, port, seed, name, password string, addr sdk.Acc
|
||||
}
|
||||
]
|
||||
}`, name, password, accnum, sequence, chainID, "steak"))
|
||||
|
||||
res, body := Request(t, port, "POST", fmt.Sprintf("/ibc/testchain/%s/send", receiveAddr), jsonStr)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
|
||||
+10
-10
@@ -78,21 +78,21 @@ func createHandler(cdc *wire.Codec) http.Handler {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ctx := context.NewCoreContextFromViper()
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc).WithLogger(os.Stdout)
|
||||
|
||||
// TODO: make more functional? aka r = keys.RegisterRoutes(r)
|
||||
r.HandleFunc("/version", CLIVersionRequestHandler).Methods("GET")
|
||||
r.HandleFunc("/node_version", NodeVersionRequestHandler(ctx)).Methods("GET")
|
||||
r.HandleFunc("/node_version", NodeVersionRequestHandler(cliCtx)).Methods("GET")
|
||||
|
||||
keys.RegisterRoutes(r)
|
||||
rpc.RegisterRoutes(ctx, r)
|
||||
tx.RegisterRoutes(ctx, r, cdc)
|
||||
auth.RegisterRoutes(ctx, r, cdc, "acc")
|
||||
bank.RegisterRoutes(ctx, r, cdc, kb)
|
||||
ibc.RegisterRoutes(ctx, r, cdc, kb)
|
||||
stake.RegisterRoutes(ctx, r, cdc, kb)
|
||||
slashing.RegisterRoutes(ctx, r, cdc, kb)
|
||||
gov.RegisterRoutes(ctx, r, cdc)
|
||||
rpc.RegisterRoutes(cliCtx, r)
|
||||
tx.RegisterRoutes(cliCtx, r, cdc)
|
||||
auth.RegisterRoutes(cliCtx, r, cdc, "acc")
|
||||
bank.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
ibc.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
stake.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
slashing.RegisterRoutes(cliCtx, r, cdc, kb)
|
||||
gov.RegisterRoutes(cliCtx, r, cdc)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -15,14 +15,15 @@ func CLIVersionRequestHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// connected node version REST handler endpoint
|
||||
func NodeVersionRequestHandler(ctx context.CoreContext) http.HandlerFunc {
|
||||
func NodeVersionRequestHandler(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
version, err := ctx.Query("/app/version")
|
||||
version, err := cliCtx.Query("/app/version")
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte(fmt.Sprintf("Could't query version. Error: %s", err.Error())))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(version)
|
||||
}
|
||||
}
|
||||
|
||||
+14
-14
@@ -5,11 +5,11 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,9 +31,9 @@ func BlockCommand() *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getBlock(ctx context.CoreContext, height *int64) ([]byte, error) {
|
||||
func getBlock(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
// get the node
|
||||
node, err := ctx.GetNode()
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -57,8 +57,8 @@ func getBlock(ctx context.CoreContext, height *int64) ([]byte, error) {
|
||||
}
|
||||
|
||||
// get the current blockchain height
|
||||
func GetChainHeight(ctx context.CoreContext) (int64, error) {
|
||||
node, err := ctx.GetNode()
|
||||
func GetChainHeight(cliCtx context.CLIContext) (int64, error) {
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func printBlock(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
output, err := getBlock(context.NewCoreContextFromViper(), height)
|
||||
output, err := getBlock(context.NewCLIContext(), height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func printBlock(cmd *cobra.Command, args []string) error {
|
||||
// REST
|
||||
|
||||
// REST handler to get a block
|
||||
func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func BlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
||||
@@ -106,13 +106,13 @@ func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'."))
|
||||
return
|
||||
}
|
||||
chainHeight, err := GetChainHeight(ctx)
|
||||
chainHeight, err := GetChainHeight(cliCtx)
|
||||
if height > chainHeight {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
|
||||
return
|
||||
}
|
||||
output, err := getBlock(ctx, &height)
|
||||
output, err := getBlock(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
@@ -123,15 +123,15 @@ func BlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// REST handler to get the latest block
|
||||
func LatestBlockRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func LatestBlockRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
height, err := GetChainHeight(ctx)
|
||||
height, err := GetChainHeight(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
output, err := getBlock(ctx, &height)
|
||||
output, err := getBlock(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
||||
+7
-7
@@ -44,11 +44,11 @@ func initClientCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
// Register REST endpoints
|
||||
func RegisterRoutes(ctx context.CoreContext, r *mux.Router) {
|
||||
r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(ctx)).Methods("GET")
|
||||
r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(ctx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(ctx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(ctx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(ctx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(ctx)).Methods("GET")
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router) {
|
||||
r.HandleFunc("/node_info", NodeInfoRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/syncing", NodeSyncingRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/latest", LatestBlockRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/blocks/{height}", BlockRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/latest", LatestValidatorSetRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/validatorsets/{height}", ValidatorSetRequestHandlerFn(cliCtx)).Methods("GET")
|
||||
}
|
||||
|
||||
+11
-7
@@ -18,23 +18,25 @@ func statusCommand() *cobra.Command {
|
||||
Short: "Query remote node for status",
|
||||
RunE: printNodeStatus,
|
||||
}
|
||||
|
||||
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:26657", "Node to connect to")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getNodeStatus(ctx context.CoreContext) (*ctypes.ResultStatus, error) {
|
||||
func getNodeStatus(cliCtx context.CLIContext) (*ctypes.ResultStatus, error) {
|
||||
// get the node
|
||||
node, err := ctx.GetNode()
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return &ctypes.ResultStatus{}, err
|
||||
}
|
||||
|
||||
return node.Status()
|
||||
}
|
||||
|
||||
// CMD
|
||||
|
||||
func printNodeStatus(cmd *cobra.Command, args []string) error {
|
||||
status, err := getNodeStatus(context.NewCoreContextFromViper())
|
||||
status, err := getNodeStatus(context.NewCLIContext())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -52,9 +54,9 @@ func printNodeStatus(cmd *cobra.Command, args []string) error {
|
||||
// REST
|
||||
|
||||
// REST handler for node info
|
||||
func NodeInfoRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func NodeInfoRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := getNodeStatus(ctx)
|
||||
status, err := getNodeStatus(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
@@ -68,14 +70,15 @@ func NodeInfoRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
||||
|
||||
// REST handler for node syncing
|
||||
func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func NodeSyncingRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
status, err := getNodeStatus(ctx)
|
||||
status, err := getNodeStatus(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
@@ -88,6 +91,7 @@ func NodeSyncingRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write([]byte(strconv.FormatBool(syncing)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,9 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) {
|
||||
func getValidators(cliCtx context.CLIContext, height *int64) ([]byte, error) {
|
||||
// get the node
|
||||
node, err := ctx.GetNode()
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,6 +74,7 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) {
|
||||
BlockHeight: validatorsRes.BlockHeight,
|
||||
Validators: make([]ValidatorOutput, len(validatorsRes.Validators)),
|
||||
}
|
||||
|
||||
for i := 0; i < len(validatorsRes.Validators); i++ {
|
||||
outputValidatorsRes.Validators[i], err = bech32ValidatorOutput(validatorsRes.Validators[i])
|
||||
if err != nil {
|
||||
@@ -85,6 +86,7 @@ func getValidators(ctx context.CoreContext, height *int64) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
@@ -104,7 +106,7 @@ func printValidators(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
}
|
||||
|
||||
output, err := getValidators(context.NewCoreContextFromViper(), height)
|
||||
output, err := getValidators(context.NewCLIContext(), height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,22 +118,25 @@ func printValidators(cmd *cobra.Command, args []string) error {
|
||||
// REST
|
||||
|
||||
// Validator Set at a height REST handler
|
||||
func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
height, err := strconv.ParseInt(vars["height"], 10, 64)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'."))
|
||||
return
|
||||
}
|
||||
chainHeight, err := GetChainHeight(ctx)
|
||||
|
||||
chainHeight, err := GetChainHeight(cliCtx)
|
||||
if height > chainHeight {
|
||||
w.WriteHeader(404)
|
||||
w.Write([]byte("ERROR: Requested block height is bigger then the chain length."))
|
||||
return
|
||||
}
|
||||
output, err := getValidators(ctx, &height)
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
@@ -143,20 +148,22 @@ func ValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Latest Validator Set REST handler
|
||||
func LatestValidatorSetRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
height, err := GetChainHeight(ctx)
|
||||
height, err := GetChainHeight(cliCtx)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
output, err := getValidators(ctx, &height)
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type BroadcastTxBody struct {
|
||||
}
|
||||
|
||||
// BroadcastTx REST Handler
|
||||
func BroadcastTxRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
func BroadcastTxRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var m BroadcastTxBody
|
||||
|
||||
@@ -25,7 +25,7 @@ func BroadcastTxRequestHandlerFn(ctx context.CoreContext) http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
res, err := ctx.BroadcastTx([]byte(m.TxBytes))
|
||||
res, err := cliCtx.BroadcastTx([]byte(m.TxBytes))
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
||||
+15
-12
@@ -21,24 +21,25 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth"
|
||||
)
|
||||
|
||||
// Get the default command for a tx query
|
||||
// QueryTxCmd implements the default command for a tx query.
|
||||
func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "tx [hash]",
|
||||
Short: "Matches this txhash over all committed blocks",
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
// find the key to look up the account
|
||||
hashHexStr := args[0]
|
||||
trustNode := viper.GetBool(client.FlagTrustNode)
|
||||
|
||||
output, err := queryTx(cdc, context.NewCoreContextFromViper(), hashHexStr, trustNode)
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(string(output))
|
||||
|
||||
fmt.Println(string(output))
|
||||
return nil
|
||||
},
|
||||
}
|
||||
@@ -50,14 +51,13 @@ func QueryTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustNode bool) ([]byte, error) {
|
||||
func queryTx(cdc *wire.Codec, cliCtx context.CLIContext, hashHexStr string, trustNode bool) ([]byte, error) {
|
||||
hash, err := hex.DecodeString(hashHexStr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// get the node
|
||||
node, err := ctx.GetNode()
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -66,6 +66,7 @@ func queryTx(cdc *wire.Codec, ctx context.CoreContext, hashHexStr string, trustN
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info, err := formatTxResult(cdc, res)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -81,13 +82,12 @@ func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
|
||||
return txInfo{}, err
|
||||
}
|
||||
|
||||
info := txInfo{
|
||||
return txInfo{
|
||||
Hash: res.Hash,
|
||||
Height: res.Height,
|
||||
Tx: tx,
|
||||
Result: res.TxResult,
|
||||
}
|
||||
return info, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
// txInfo is used to prepare info to display
|
||||
@@ -100,17 +100,19 @@ type txInfo struct {
|
||||
|
||||
func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
|
||||
var tx auth.StdTx
|
||||
|
||||
err := cdc.UnmarshalBinary(txBytes, &tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tx, nil
|
||||
}
|
||||
|
||||
// REST
|
||||
|
||||
// transaction query REST handler
|
||||
func QueryTxRequestHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.HandlerFunc {
|
||||
func QueryTxRequestHandlerFn(cdc *wire.Codec, cliCtx context.CLIContext) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
hashHexStr := vars["hash"]
|
||||
@@ -120,12 +122,13 @@ func QueryTxRequestHandlerFn(cdc *wire.Codec, ctx context.CoreContext) http.Hand
|
||||
trustNode = true
|
||||
}
|
||||
|
||||
output, err := queryTx(cdc, ctx, hashHexStr, trustNode)
|
||||
output, err := queryTx(cdc, cliCtx, hashHexStr, trustNode)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
w.Write(output)
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -17,9 +17,9 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
|
||||
}
|
||||
|
||||
// register REST routes
|
||||
func RegisterRoutes(ctx context.CoreContext, r *mux.Router, cdc *wire.Codec) {
|
||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, ctx)).Methods("GET")
|
||||
r.HandleFunc("/txs", SearchTxRequestHandlerFn(ctx, cdc)).Methods("GET")
|
||||
func RegisterRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *wire.Codec) {
|
||||
r.HandleFunc("/txs/{hash}", QueryTxRequestHandlerFn(cdc, cliCtx)).Methods("GET")
|
||||
r.HandleFunc("/txs", SearchTxRequestHandlerFn(cliCtx, cdc)).Methods("GET")
|
||||
// r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST")
|
||||
// r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST")
|
||||
}
|
||||
|
||||
+20
-10
@@ -7,15 +7,15 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/wire"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
ctypes "github.com/tendermint/tendermint/rpc/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -31,14 +31,18 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
tags := viper.GetStringSlice(flagTags)
|
||||
|
||||
txs, err := searchTxs(context.NewCoreContextFromViper(), cdc, tags)
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
txs, err := searchTxs(cliCtx, cdc, tags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
output, err := cdc.MarshalJSON(txs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Println(string(output))
|
||||
return nil
|
||||
},
|
||||
@@ -53,19 +57,22 @@ func SearchTxCmd(cdc *wire.Codec) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func searchTxs(ctx context.CoreContext, cdc *wire.Codec, tags []string) ([]txInfo, error) {
|
||||
func searchTxs(cliCtx context.CLIContext, cdc *wire.Codec, tags []string) ([]txInfo, error) {
|
||||
if len(tags) == 0 {
|
||||
return nil, errors.New("must declare at least one tag to search")
|
||||
}
|
||||
|
||||
// XXX: implement ANY
|
||||
query := strings.Join(tags, " AND ")
|
||||
|
||||
// get the node
|
||||
node, err := ctx.GetNode()
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prove := !viper.GetBool(client.FlagTrustNode)
|
||||
|
||||
// TODO: take these as args
|
||||
page := 0
|
||||
perPage := 100
|
||||
@@ -98,7 +105,7 @@ func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error)
|
||||
// REST
|
||||
|
||||
// Search Tx REST Handler
|
||||
func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.HandlerFunc {
|
||||
func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *wire.Codec) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tag := r.FormValue("tag")
|
||||
if tag == "" {
|
||||
@@ -106,14 +113,17 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han
|
||||
w.Write([]byte("You need to provide at least a tag as a key=value pair to search for. Postfix the key with _bech32 to search bech32-encoded addresses or public keys"))
|
||||
return
|
||||
}
|
||||
|
||||
keyValue := strings.Split(tag, "=")
|
||||
key := keyValue[0]
|
||||
|
||||
value, err := url.QueryUnescape(keyValue[1])
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Could not decode address: " + err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasSuffix(key, "_bech32") {
|
||||
bech32address := strings.Trim(value, "'")
|
||||
prefix := strings.Split(bech32address, "1")[0]
|
||||
@@ -127,7 +137,7 @@ func SearchTxRequestHandlerFn(ctx context.CoreContext, cdc *wire.Codec) http.Han
|
||||
tag = strings.TrimRight(key, "_bech32") + "='" + sdk.AccAddress(bz).String() + "'"
|
||||
}
|
||||
|
||||
txs, err := searchTxs(ctx, cdc, []string{tag})
|
||||
txs, err := searchTxs(cliCtx, cdc, []string{tag})
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte(err.Error()))
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/keys"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authctx "github.com/cosmos/cosmos-sdk/x/auth/client/context"
|
||||
)
|
||||
|
||||
// SendTx implements a auxiliary handler that facilitates sending a series of
|
||||
// messages in a signed transaction given a TxContext and a QueryContext. It
|
||||
// ensures that the account exists, has a proper number and sequence set. In
|
||||
// addition, it builds and signs a transaction with the supplied messages.
|
||||
// Finally, it broadcasts the signed transaction to a node.
|
||||
func SendTx(txCtx authctx.TxContext, cliCtx context.CLIContext, msgs []sdk.Msg) error {
|
||||
if err := cliCtx.EnsureAccountExists(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
from, err := cliCtx.GetFromAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account number without
|
||||
// automatically doing a manual lookup.
|
||||
if txCtx.AccountNumber == 0 {
|
||||
accNum, err := cliCtx.GetAccountNumber(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCtx = txCtx.WithAccountNumber(accNum)
|
||||
}
|
||||
|
||||
// TODO: (ref #1903) Allow for user supplied account sequence without
|
||||
// automatically doing a manual lookup.
|
||||
if txCtx.Sequence == 0 {
|
||||
accSeq, err := cliCtx.GetAccountSequence(from)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCtx = txCtx.WithSequence(accSeq)
|
||||
}
|
||||
|
||||
passphrase, err := keys.GetPassphrase(cliCtx.FromAddressName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// build and sign the transaction
|
||||
txBytes, err := txCtx.BuildAndSign(cliCtx.FromAddressName, passphrase, msgs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// broadcast to a Tendermint node
|
||||
return cliCtx.EnsureBroadcastTx(txBytes)
|
||||
}
|
||||
Reference in New Issue
Block a user