diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000000..404f6c39fbff --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +Dockerfile +Vagrantfile + +build/ +coverage.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 0566da4eb20d..54e994bdf6aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## 0.15.0 (TBD) +FEATURES: + +* Add CacheContext +* Add auto sequencing to client + BREAKING CHANGES * Remove go-wire, use go-amino diff --git a/Dockerfile b/Dockerfile index 79b078d91caa..a788aa6416ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,28 +1,34 @@ -FROM alpine:3.5 +# Simple usage with a mounted data directory: +# > docker build -t gaia . +# > docker run -v $HOME/.gaiad:/root/.gaiad gaia init +# > docker run -v $HOME/.gaiad:/root/.gaiad gaia start -# BCHOME is where your genesis.json, key.json and other files including state are stored. -ENV BCHOME /basecoin +FROM alpine:edge -# Create a basecoin user and group first so the IDs get set the same way, even -# as the rest of this may change over time. -RUN addgroup basecoin && \ - adduser -S -G basecoin basecoin +# Set up dependencies +ENV PACKAGES go glide make git libc-dev bash -RUN mkdir -p $BCHOME && \ - chown -R basecoin:basecoin $BCHOME -WORKDIR $BCHOME +# Set up GOPATH & PATH -# Expose the basecoin home directory as a volume since there's mutable state in there. -VOLUME $BCHOME +ENV GOPATH /root/go +ENV BASE_PATH $GOPATH/src/github.com/cosmos +ENV REPO_PATH $BASE_PATH/cosmos-sdk +ENV WORKDIR /cosmos/ +ENV PATH $GOPATH/bin:$PATH -# jq and curl used for extracting `pub_key` from private validator while -# deploying tendermint with Kubernetes. It is nice to have bash so the users -# could execute bash commands. -RUN apk add --no-cache bash curl jq +# Link expected Go repo path -COPY basecoin /usr/bin/basecoin +RUN mkdir -p $WORKDIR $GOPATH/pkg $ $GOPATH/bin $BASE_PATH -ENTRYPOINT ["basecoin"] +# Add source files -# By default you will get the basecoin with local MerkleEyes and in-proc Tendermint. -CMD ["start", "--dir=${BCHOME}"] +ADD . $REPO_PATH + +# Install minimum necessary dependencies, build Cosmos SDK, remove packages +RUN apk add --no-cache $PACKAGES && \ + cd $REPO_PATH && make get_tools && make get_vendor_deps && make all && make install && \ + apk del $PACKAGES + +# Set entrypoint + +ENTRYPOINT ["gaiad"] diff --git a/client/context/viper.go b/client/context/viper.go index 750a37c616cc..c3b0369d98a9 100644 --- a/client/context/viper.go +++ b/client/context/viper.go @@ -1,27 +1,72 @@ package context import ( + "fmt" "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/core" ) +// NewCoreContextFromViper - return a new context with parameters from the command line func NewCoreContextFromViper() core.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 + } + } return core.CoreContext{ - ChainID: viper.GetString(client.FlagChainID), + ChainID: chainID, Height: viper.GetInt64(client.FlagHeight), TrustNode: viper.GetBool(client.FlagTrustNode), FromAddressName: viper.GetString(client.FlagName), NodeURI: nodeURI, Sequence: viper.GetInt64(client.FlagSequence), Client: rpc, + Decoder: nil, + AccountStore: "main", + } +} + +// 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 +} + +// EnsureSequence - automatically set sequence number if none provided +func EnsureSequence(ctx core.CoreContext) (core.CoreContext, error) { + if viper.IsSet(client.FlagSequence) { + 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 } diff --git a/client/core/context.go b/client/core/context.go index 3d7f400a8e12..fac60b553bf7 100644 --- a/client/core/context.go +++ b/client/core/context.go @@ -2,6 +2,8 @@ package core import ( rpcclient "github.com/tendermint/tendermint/rpc/client" + + sdk "github.com/cosmos/cosmos-sdk/types" ) type CoreContext struct { @@ -12,39 +14,61 @@ type CoreContext struct { FromAddressName string Sequence int64 Client rpcclient.Client + Decoder sdk.AccountDecoder + AccountStore string } +// 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 } +// 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 updated sequence number func (c CoreContext) WithSequence(sequence int64) CoreContext { c.Sequence = sequence 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 sdk.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 +} diff --git a/client/core/core.go b/client/core/core.go index a5c7b340c7ec..2a1b2736a229 100644 --- a/client/core/core.go +++ b/client/core/core.go @@ -91,6 +91,9 @@ func (ctx CoreContext) SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *w // build the Sign Messsage from the Standard Message chainID := ctx.ChainID + if chainID == "" { + return nil, errors.Errorf("Chain ID required but not specified") + } sequence := ctx.Sequence signMsg := sdk.StdSignMsg{ ChainID: chainID, @@ -137,6 +140,25 @@ func (ctx CoreContext) SignBuildBroadcast(name string, msg sdk.Msg, cdc *wire.Co return ctx.BroadcastTx(txBytes) } +// get the next sequence for the account address +func (c CoreContext) NextSequence(address []byte) (int64, error) { + if c.Decoder == nil { + return 0, errors.New("AccountDecoder required but not provided") + } + + res, err := c.Query(address, c.AccountStore) + if err != nil { + return 0, err + } + + account, err := c.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() diff --git a/examples/democoin/x/cool/commands/tx.go b/examples/democoin/x/cool/commands/tx.go index 8deaac405d9a..88c5e4e68a89 100644 --- a/examples/democoin/x/cool/commands/tx.go +++ b/examples/democoin/x/cool/commands/tx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/context" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/examples/democoin/x/cool" ) @@ -24,7 +25,7 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { return errors.New("You must provide an answer") } - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) // get the from address from the name flag from, err := ctx.GetFromAddress() @@ -38,6 +39,12 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { // get account name name := viper.GetString(client.FlagName) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(name, msg, cdc) if err != nil { @@ -60,7 +67,7 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { return errors.New("You must provide an answer") } - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) // get the from address from the name flag from, err := ctx.GetFromAddress() @@ -71,6 +78,12 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { // get account name name := viper.GetString(client.FlagName) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // create the message msg := cool.NewSetTrendMsg(from, args[0]) diff --git a/examples/democoin/x/pow/commands/tx.go b/examples/democoin/x/pow/commands/tx.go index badbe39092c2..5fa11a476667 100644 --- a/examples/democoin/x/pow/commands/tx.go +++ b/examples/democoin/x/pow/commands/tx.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/examples/democoin/x/pow" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" ) func MineCmd(cdc *wire.Codec) *cobra.Command { @@ -24,7 +25,7 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { // get from address and parse arguments - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) from, err := ctx.GetFromAddress() if err != nil { @@ -53,6 +54,12 @@ func MineCmd(cdc *wire.Codec) *cobra.Command { // get account name name := ctx.FromAddressName + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(name, msg, cdc) if err != nil { diff --git a/types/context.go b/types/context.go index 3c90b016a226..8c91175bc637 100644 --- a/types/context.go +++ b/types/context.go @@ -171,6 +171,14 @@ func (c Context) WithTxBytes(txBytes []byte) Context { return c.withValue(contextKeyTxBytes, txBytes) } +// Cache the multistore and return a new cached context. The cached context is +// written to the context when writeCache is called. +func (c Context) CacheContext() (cc Context, writeCache func()) { + cms := c.multiStore().CacheMultiStore() + cc = c.WithMultiStore(cms) + return cc, cms.Write +} + //---------------------------------------- // thePast diff --git a/types/context_test.go b/types/context_test.go index 36d8099b9531..b40e79dc2a8a 100644 --- a/types/context_test.go +++ b/types/context_test.go @@ -3,6 +3,11 @@ package types_test import ( "testing" + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + + "github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" ) @@ -18,3 +23,39 @@ func TestContextGetOpShouldNeverPanic(t *testing.T) { _, _ = ctx.GetOp(index) } } + +func defaultContext(key types.StoreKey) types.Context { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, types.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := types.NewContext(cms, abci.Header{}, false, nil) + return ctx +} + +func TestCacheContext(t *testing.T) { + key := types.NewKVStoreKey(t.Name()) + k1 := []byte("hello") + v1 := []byte("world") + k2 := []byte("key") + v2 := []byte("value") + + ctx := defaultContext(key) + store := ctx.KVStore(key) + store.Set(k1, v1) + assert.Equal(t, v1, store.Get(k1)) + assert.Nil(t, store.Get(k2)) + + cctx, write := ctx.CacheContext() + cstore := cctx.KVStore(key) + assert.Equal(t, v1, cstore.Get(k1)) + assert.Nil(t, cstore.Get(k2)) + + cstore.Set(k2, v2) + assert.Equal(t, v2, cstore.Get(k2)) + assert.Nil(t, store.Get(k2)) + + write() + + assert.Equal(t, v2, store.Get(k2)) +} diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index aa0e17a9103c..56048262d73d 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -10,6 +10,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/bank" ) @@ -36,7 +37,7 @@ type Commander struct { } func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(c.Cdc)) // get the from address from, err := ctx.GetFromAddress() @@ -62,6 +63,12 @@ func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { // build message msg := BuildMsg(from, to, coins) + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.Cdc) if err != nil { diff --git a/x/ibc/commands/ibctx.go b/x/ibc/commands/ibctx.go index 689a98318122..d17b40b21edf 100644 --- a/x/ibc/commands/ibctx.go +++ b/x/ibc/commands/ibctx.go @@ -13,6 +13,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/ibc" ) @@ -39,7 +40,7 @@ type sendCommander struct { } func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(c.cdc)) // get the from address from, err := ctx.GetFromAddress() @@ -53,6 +54,12 @@ func (c sendCommander) sendIBCTransfer(cmd *cobra.Command, args []string) error return err } + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + // get password res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, c.cdc) if err != nil { diff --git a/x/simplestake/commands/commands.go b/x/simplestake/commands/commands.go index b2a057beeda5..ba6602028313 100644 --- a/x/simplestake/commands/commands.go +++ b/x/simplestake/commands/commands.go @@ -12,6 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/simplestake" ) @@ -94,7 +95,14 @@ func (co commander) unbondTxCmd(cmd *cobra.Command, args []string) error { } func (co commander) sendMsg(msg sdk.Msg) error { - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(co.cdc)) + + // default to next sequence number if none provided + ctx, err := context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, co.cdc) if err != nil { return err diff --git a/x/stake/commands/tx.go b/x/stake/commands/tx.go index d1a399e19ce5..5cc9747b6143 100644 --- a/x/stake/commands/tx.go +++ b/x/stake/commands/tx.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/context" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands" "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -92,7 +93,14 @@ func GetCmdDeclareCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDeclareCandidacy(candidateAddr, pk, amount, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -129,7 +137,14 @@ func GetCmdEditCandidacy(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgEditCandidacy(candidateAddr, description) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -165,7 +180,14 @@ func GetCmdDelegate(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgDelegate(delegatorAddr, candidateAddr, amount) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err @@ -212,7 +234,14 @@ func GetCmdUnbond(cdc *wire.Codec) *cobra.Command { msg := stake.NewMsgUnbond(delegatorAddr, candidateAddr, sharesStr) // build and sign the transaction, then broadcast to Tendermint - ctx := context.NewCoreContextFromViper() + ctx := context.NewCoreContextFromViper().WithDecoder(authcmd.GetAccountDecoder(cdc)) + + // default to next sequence number if none provided + ctx, err = context.EnsureSequence(ctx) + if err != nil { + return err + } + res, err := ctx.SignBuildBroadcast(ctx.FromAddressName, msg, cdc) if err != nil { return err