Merge PR #1741: CoreContext Refactor

This commit is contained in:
Alexander Bezobchuk
2018-08-06 20:11:30 +02:00
committed by Christopher Goes
parent 3c4985c315
commit 12c2c236c2
48 changed files with 1521 additions and 1195 deletions
+115
View File
@@ -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
}
+13
View File
@@ -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)
}
-347
View File
@@ -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
}
+311
View File
@@ -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)
}
-113
View File
@@ -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
}
-141
View File
@@ -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
}