Merge pull request #533 from cosmos/release-v0.11.0

Release v0.11.0
This commit is contained in:
Ethan Buchman 2018-03-01 02:12:31 -05:00 committed by GitHub
commit f4c2b504f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
73 changed files with 3765 additions and 622 deletions

View File

@ -1,5 +1,31 @@
# Changelog
## 0.11.0 (March 1, 2017)
BREAKING CHANGES
* [examples] dummy -> kvstore
* [examples] Remove gaia
* [examples/basecoin] MakeTxCodec -> MakeCodec
* [types] CommitMultiStore interface has new `GetCommitKVStore(key StoreKey) CommitKVStore` method
FEATURES
* [examples/basecoin] CLI for `basecli` and `basecoind` (!)
* [baseapp] router.AddRoute returns Router
IMPROVEMENTS
* [baseapp] Run msg handlers on CheckTx
* [docs] Add spec for REST API
* [all] More tests!
BUG FIXES
* [baseapp] Fix panic on app restart
* [baseapp] InitChain does not call Commit
* [basecoin] Remove IBCStore because mounting multiple stores is currently broken
## 0.10.0 (February 20, 2017)
BREAKING CHANGES

View File

@ -18,7 +18,8 @@ gaia:
build:
@rm -rf examples/basecoin/vendor/
go build $(BUILD_FLAGS) -o build/basecoind ./examples/basecoin/cmd/basecoind/...
go build $(BUILD_FLAGS) -o build/basecoind ./examples/basecoin/cmd/basecoind
go build $(BUILD_FLAGS) -o build/basecli ./examples/basecoin/cmd/basecli
dist:
@bash publish/dist.sh
@ -58,16 +59,19 @@ godocs:
########################################
### Testing
TUTORIALS=$(shell find docs/guide -name "*md" -type f)
#test: test_unit test_cli test_tutorial
test: test_unit # test_cli
# Must be run in each package seperately for the visualization
# Added here for easy reference
# coverage:
# go test -coverprofile=c.out && go tool cover -html=c.out
test_unit:
@rm -rf examples/basecoin/vendor/
@go test $(PACKAGES)
test_cover:
@rm -rf examples/basecoin/vendor/
@bash tests/test_cover.sh
benchmark:

View File

@ -4,7 +4,6 @@ import (
"fmt"
"runtime/debug"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
abci "github.com/tendermint/abci/types"
@ -16,12 +15,16 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
var mainHeaderKey = []byte("header")
// Key to store the header in the DB itself.
// Use the db directly instead of a store to avoid
// conflicts with handlers writing to the store
// and to avoid affecting the Merkle root.
var dbHeaderKey = []byte("header")
// The ABCI application
type BaseApp struct {
// initialized on creation
logger log.Logger
Logger log.Logger
name string // application name from abci.Info
db dbm.DB // common DB backend
cms sdk.CommitMultiStore // Main (uncached) state
@ -38,24 +41,22 @@ type BaseApp struct {
//--------------------
// Volatile
// .msCheck and .ctxCheck are set on initialization and reset on Commit.
// .msDeliver and .ctxDeliver are (re-)set on BeginBlock.
// .valUpdates accumulate in DeliverTx and reset in BeginBlock.
// QUESTION: should we put valUpdates in the ctxDeliver?
msCheck sdk.CacheMultiStore // CheckTx state, a cache-wrap of `.cms`
msDeliver sdk.CacheMultiStore // DeliverTx state, a cache-wrap of `.cms`
ctxCheck sdk.Context // CheckTx context
ctxDeliver sdk.Context // DeliverTx context
valUpdates []abci.Validator // cached validator changes from DeliverTx
// checkState is set on initialization and reset on Commit.
// deliverState is set in InitChain and BeginBlock and cleared on Commit.
// See methods setCheckState and setDeliverState.
// .valUpdates accumulate in DeliverTx and are reset in BeginBlock.
// QUESTION: should we put valUpdates in the deliverState.ctx?
checkState *state // for CheckTx
deliverState *state // for DeliverTx
valUpdates []abci.Validator // cached validator changes from DeliverTx
}
var _ abci.Application = &BaseApp{}
var _ abci.Application = (*BaseApp)(nil)
// Create and name new BaseApp
func NewBaseApp(name string, logger log.Logger, db dbm.DB) *BaseApp {
return &BaseApp{
logger: logger,
Logger: logger,
name: name,
db: db,
cms: store.NewCommitMultiStore(db),
@ -94,11 +95,9 @@ func (app *BaseApp) SetEndBlocker(endBlocker sdk.EndBlocker) {
app.endBlocker = endBlocker
}
func (app *BaseApp) SetAnteHandler(ah sdk.AnteHandler) {
// deducts fee from payer, verifies signatures and nonces, sets Signers to ctx.
app.anteHandler = ah
}
// nolint - Get functions
func (app *BaseApp) Router() Router { return app.router }
// load latest application version
@ -125,36 +124,46 @@ func (app *BaseApp) LastBlockHeight() int64 {
// initializes the remaining logic from app.cms
func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
var lastCommitID = app.cms.LastCommitID()
var main = app.cms.GetKVStore(mainKey)
var header abci.Header
// main store should exist.
// TODO: we don't actually need the main store here
main := app.cms.GetKVStore(mainKey)
if main == nil {
return errors.New("BaseApp expects MultiStore with 'main' KVStore")
}
// if we've committed before, we expect main://<mainHeaderKey>
if !lastCommitID.IsZero() {
headerBytes := main.Get(mainHeaderKey)
if len(headerBytes) == 0 {
errStr := fmt.Sprintf("Version > 0 but missing key %s", mainHeaderKey)
return errors.New(errStr)
// XXX: Do we really need the header? What does it have that we want
// here that's not already in the CommitID ? If an app wants to have it,
// they can do so in their BeginBlocker. If we force it in baseapp,
// then either we force the AppHash to change with every block (since the header
// will be in the merkle store) or we can't write the state and the header to the
// db atomically without doing some surgery on the store interfaces ...
// if we've committed before, we expect <dbHeaderKey> to exist in the db
/*
var lastCommitID = app.cms.LastCommitID()
var header abci.Header
if !lastCommitID.IsZero() {
headerBytes := app.db.Get(dbHeaderKey)
if len(headerBytes) == 0 {
errStr := fmt.Sprintf("Version > 0 but missing key %s", dbHeaderKey)
return errors.New(errStr)
}
err := proto.Unmarshal(headerBytes, &header)
if err != nil {
return errors.Wrap(err, "Failed to parse Header")
}
lastVersion := lastCommitID.Version
if header.Height != lastVersion {
errStr := fmt.Sprintf("Expected db://%s.Height %v but got %v", dbHeaderKey, lastVersion, header.Height)
return errors.New(errStr)
}
}
err := proto.Unmarshal(headerBytes, &header)
if err != nil {
return errors.Wrap(err, "Failed to parse Header")
}
lastVersion := lastCommitID.Version
if header.Height != lastVersion {
errStr := fmt.Sprintf("Expected main://%s.Height %v but got %v", mainHeaderKey, lastVersion, header.Height)
return errors.New(errStr)
}
}
*/
// initialize Check state
app.msCheck = app.cms.CacheMultiStore()
app.ctxCheck = app.NewContext(true, abci.Header{})
app.setCheckState(abci.Header{})
return nil
}
@ -162,9 +171,34 @@ func (app *BaseApp) initFromStore(mainKey sdk.StoreKey) error {
// NewContext returns a new Context with the correct store, the given header, and nil txBytes.
func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
if isCheckTx {
return sdk.NewContext(app.msCheck, header, true, nil)
return sdk.NewContext(app.checkState.ms, header, true, nil)
}
return sdk.NewContext(app.deliverState.ms, header, false, nil)
}
type state struct {
ms sdk.CacheMultiStore
ctx sdk.Context
}
func (st *state) CacheMultiStore() sdk.CacheMultiStore {
return st.ms.CacheMultiStore()
}
func (app *BaseApp) setCheckState(header abci.Header) {
ms := app.cms.CacheMultiStore()
app.checkState = &state{
ms: ms,
ctx: sdk.NewContext(ms, header, true, nil),
}
}
func (app *BaseApp) setDeliverState(header abci.Header) {
ms := app.cms.CacheMultiStore()
app.deliverState = &state{
ms: ms,
ctx: sdk.NewContext(ms, header, false, nil),
}
return sdk.NewContext(app.msDeliver, header, false, nil)
}
//----------------------------------------
@ -172,7 +206,6 @@ func (app *BaseApp) NewContext(isCheckTx bool, header abci.Header) sdk.Context {
// Implements ABCI
func (app *BaseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
lastCommitID := app.cms.LastCommitID()
return abci.ResponseInfo{
@ -196,16 +229,12 @@ func (app *BaseApp) InitChain(req abci.RequestInitChain) (res abci.ResponseInitC
return
}
// make a context for the initialization.
// NOTE: we're writing to the cms directly, without a CacheWrap
ctx := sdk.NewContext(app.cms, abci.Header{}, false, nil)
// Initialize the deliver state and run initChain
app.setDeliverState(abci.Header{})
app.initChainer(app.deliverState.ctx, req) // no error
res = app.initChainer(ctx, req)
// TODO: handle error https://github.com/cosmos/cosmos-sdk/issues/468
// XXX this commits everything and bumps the version.
// https://github.com/cosmos/cosmos-sdk/issues/442#issuecomment-366470148
app.cms.Commit()
// NOTE: we don't commit, but BeginBlock for block 1
// starts from this deliverState
return
}
@ -223,18 +252,22 @@ func (app *BaseApp) Query(req abci.RequestQuery) (res abci.ResponseQuery) {
// Implements ABCI
func (app *BaseApp) BeginBlock(req abci.RequestBeginBlock) (res abci.ResponseBeginBlock) {
app.msDeliver = app.cms.CacheMultiStore()
app.ctxDeliver = app.NewContext(false, req.Header)
// Initialize the DeliverTx state.
// If this is the first block, it should already
// be initialized in InitChain. It may also be nil
// if this is a test and InitChain was never called.
if app.deliverState == nil {
app.setDeliverState(req.Header)
}
app.valUpdates = nil
if app.beginBlocker != nil {
res = app.beginBlocker(app.ctxDeliver, req)
res = app.beginBlocker(app.deliverState.ctx, req)
}
return
}
// Implements ABCI
func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
// Decode the Tx.
var result sdk.Result
var tx, err = app.txDecoder(txBytes)
@ -259,7 +292,6 @@ func (app *BaseApp) CheckTx(txBytes []byte) (res abci.ResponseCheckTx) {
// Implements ABCI
func (app *BaseApp) DeliverTx(txBytes []byte) (res abci.ResponseDeliverTx) {
// Decode the Tx.
var result sdk.Result
var tx, err = app.txDecoder(txBytes)
@ -300,7 +332,6 @@ func (app *BaseApp) Deliver(tx sdk.Tx) (result sdk.Result) {
// txBytes may be nil in some cases, eg. in tests.
// Also, in the future we may support "internal" transactions.
func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk.Result) {
// Handle any panics.
defer func() {
if r := recover(); r != nil {
@ -324,32 +355,41 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
// Get the context
var ctx sdk.Context
if isCheckTx {
ctx = app.ctxCheck.WithTxBytes(txBytes)
ctx = app.checkState.ctx.WithTxBytes(txBytes)
} else {
ctx = app.ctxDeliver.WithTxBytes(txBytes)
ctx = app.deliverState.ctx.WithTxBytes(txBytes)
}
// TODO: override default ante handler w/ custom ante handler.
// Run the ante handler.
newCtx, result, abort := app.anteHandler(ctx, tx)
if isCheckTx || abort {
return result
}
if !newCtx.IsZero() {
ctx = newCtx
if app.anteHandler != nil {
newCtx, result, abort := app.anteHandler(ctx, tx)
if abort {
return result
}
if !newCtx.IsZero() {
ctx = newCtx
}
}
// CacheWrap app.msDeliver in case it fails.
msCache := app.msDeliver.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
// Get the correct cache
var msCache sdk.CacheMultiStore
if isCheckTx == true {
// CacheWrap app.checkState.ms in case it fails.
msCache = app.checkState.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
} else {
// CacheWrap app.deliverState.ms in case it fails.
msCache = app.deliverState.CacheMultiStore()
ctx = ctx.WithMultiStore(msCache)
}
// Match and run route.
msgType := msg.Type()
handler := app.router.Route(msgType)
result = handler(ctx, msg)
// If result was successful, write to app.msDeliver or app.msCheck.
// If result was successful, write to app.checkState.ms or app.deliverState.ms
if result.IsOK() {
msCache.Write()
}
@ -360,7 +400,7 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk
// Implements ABCI
func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBlock) {
if app.endBlocker != nil {
res = app.endBlocker(app.ctxDeliver, req)
res = app.endBlocker(app.deliverState.ctx, req)
} else {
res.ValidatorUpdates = app.valUpdates
}
@ -369,19 +409,30 @@ func (app *BaseApp) EndBlock(req abci.RequestEndBlock) (res abci.ResponseEndBloc
// Implements ABCI
func (app *BaseApp) Commit() (res abci.ResponseCommit) {
header := app.deliverState.ctx.BlockHeader()
/*
// Write the latest Header to the store
headerBytes, err := proto.Marshal(&header)
if err != nil {
panic(err)
}
app.db.SetSync(dbHeaderKey, headerBytes)
*/
// Write the Deliver state and commit the MultiStore
app.msDeliver.Write()
app.deliverState.ms.Write()
commitID := app.cms.Commit()
app.logger.Debug("Commit synced",
app.Logger.Debug("Commit synced",
"commit", commitID,
)
// Reset the Check state
// Reset the Check state to the latest committed
// NOTE: safe because Tendermint holds a lock on the mempool for Commit.
// Use the header from this latest block.
header := app.ctxDeliver.BlockHeader()
app.msCheck = app.cms.CacheMultiStore()
app.ctxCheck = app.NewContext(true, header)
app.setCheckState(header)
// Emtpy the Deliver state
app.deliverState = nil
return abci.ResponseCommit{
Data: commitID.Hash,

View File

@ -42,31 +42,59 @@ func TestMountStores(t *testing.T) {
app.MountStoresIAVL(capKey1, capKey2)
// both stores are mounted
// stores are mounted
err := app.LoadLatestVersion(capKey1)
assert.Nil(t, err)
err = app.LoadLatestVersion(capKey2)
assert.Nil(t, err)
// check both stores
store1 := app.cms.GetCommitKVStore(capKey1)
assert.NotNil(t, store1)
store2 := app.cms.GetCommitKVStore(capKey2)
assert.NotNil(t, store2)
}
// Test that we can make commits and then reload old versions.
// Test that LoadLatestVersion actually does.
func TestLoadVersion(t *testing.T) {
// TODO
// Test that we can make commits and then reload old versions.
// Test that LoadLatestVersion actually does.
logger := defaultLogger()
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db)
// make a cap key and mount the store
capKey := sdk.NewKVStoreKey("main")
app.MountStoresIAVL(capKey)
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
assert.Nil(t, err)
emptyCommitID := sdk.CommitID{}
lastHeight := app.LastBlockHeight()
lastID := app.LastCommitID()
assert.Equal(t, int64(0), lastHeight)
assert.Equal(t, emptyCommitID, lastID)
// execute some blocks
header := abci.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID := sdk.CommitID{1, res.Data}
// reload
app = NewBaseApp(name, logger, db)
app.MountStoresIAVL(capKey)
err = app.LoadLatestVersion(capKey) // needed to make stores non-nil
assert.Nil(t, err)
lastHeight = app.LastBlockHeight()
lastID = app.LastCommitID()
assert.Equal(t, int64(1), lastHeight)
assert.Equal(t, commitID, lastID)
}
func TestTxDecoder(t *testing.T) {
// TODO
// Test that txs can be unmarshalled and read and that
// correct error codes are returned when not
}
func TestInfo(t *testing.T) {
// TODO
// Test that Info returns the latest committed state.
}
func TestInitChainer(t *testing.T) {
// Test that the app hash is static
// TODO: https://github.com/cosmos/cosmos-sdk/issues/520
/*func TestStaticAppHash(t *testing.T) {
app := newBaseApp(t.Name())
// make a cap key and mount the store
@ -75,6 +103,62 @@ func TestInitChainer(t *testing.T) {
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
assert.Nil(t, err)
// execute some blocks
header := abci.Header{Height: 1}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res := app.Commit()
commitID1 := sdk.CommitID{1, res.Data}
header = abci.Header{Height: 2}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
res = app.Commit()
commitID2 := sdk.CommitID{2, res.Data}
assert.Equal(t, commitID1.Hash, commitID2.Hash)
}
*/
// Test that txs can be unmarshalled and read and that
// correct error codes are returned when not
func TestTxDecoder(t *testing.T) {
// TODO
}
// Test that Info returns the latest committed state.
func TestInfo(t *testing.T) {
app := newBaseApp(t.Name())
// ----- test an empty response -------
reqInfo := abci.RequestInfo{}
res := app.Info(reqInfo)
// should be empty
assert.Equal(t, "", res.Version)
assert.Equal(t, t.Name(), res.GetData())
assert.Equal(t, int64(0), res.LastBlockHeight)
assert.Equal(t, []uint8(nil), res.LastBlockAppHash)
// ----- test a proper response -------
// TODO
}
func TestInitChainer(t *testing.T) {
logger := defaultLogger()
db := dbm.NewMemDB()
name := t.Name()
app := NewBaseApp(name, logger, db)
// make cap keys and mount the stores
// NOTE/TODO: mounting multiple stores is broken
// see https://github.com/cosmos/cosmos-sdk/issues/532
capKey := sdk.NewKVStoreKey("main")
// capKey2 := sdk.NewKVStoreKey("key2")
app.MountStoresIAVL(capKey) // , capKey2)
err := app.LoadLatestVersion(capKey) // needed to make stores non-nil
assert.Nil(t, err)
key, value := []byte("hello"), []byte("goodbye")
// initChainer sets a value in the store
@ -97,18 +181,38 @@ func TestInitChainer(t *testing.T) {
// set initChainer and try again - should see the value
app.SetInitChainer(initChainer)
app.InitChain(abci.RequestInitChain{})
app.Commit()
res = app.Query(query)
assert.Equal(t, value, res.Value)
// reload app
app = NewBaseApp(name, logger, db)
capKey = sdk.NewKVStoreKey("main")
// capKey2 = sdk.NewKVStoreKey("key2") // TODO
app.MountStoresIAVL(capKey) //, capKey2)
err = app.LoadLatestVersion(capKey) // needed to make stores non-nil
assert.Nil(t, err)
app.SetInitChainer(initChainer)
// ensure we can still query after reloading
res = app.Query(query)
assert.Equal(t, value, res.Value)
// commit and ensure we can still query
app.BeginBlock(abci.RequestBeginBlock{})
app.Commit()
res = app.Query(query)
assert.Equal(t, value, res.Value)
}
// Test that successive CheckTx can see eachothers effects
// Test that successive CheckTx can see each others' effects
// on the store within a block, and that the CheckTx state
// gets reset to the latest Committed state during Commit
func TestCheckTx(t *testing.T) {
// TODO
}
// Test that successive DeliverTx can see eachothers effects
// Test that successive DeliverTx can see each others' effects
// on the store, both within and across blocks.
func TestDeliverTx(t *testing.T) {
app := newBaseApp(t.Name())
@ -231,7 +335,8 @@ func TestValidatorChange(t *testing.T) {
// Create app.
app := newBaseApp(t.Name())
storeKeys := createMounts(app.cms)
capKey := sdk.NewKVStoreKey("key")
app.MountStoresIAVL(capKey)
app.SetTxDecoder(func(txBytes []byte) (sdk.Tx, sdk.Error) {
var ttx testUpdatePowerTx
fromJSON(txBytes, &ttx)
@ -245,7 +350,7 @@ func TestValidatorChange(t *testing.T) {
})
// Load latest state, which should be empty.
err := app.LoadLatestVersion(storeKeys["main"])
err := app.LoadLatestVersion(capKey)
assert.Nil(t, err)
assert.Equal(t, app.LastBlockHeight(), int64(0))
@ -354,17 +459,3 @@ func fromJSON(bz []byte, ptr interface{}) {
panic(err)
}
}
// Mounts stores to CommitMultiStore and returns a map of keys.
func createMounts(ms sdk.CommitMultiStore) map[string]sdk.StoreKey {
dbMain := dbm.NewMemDB()
dbXtra := dbm.NewMemDB()
keyMain := sdk.NewKVStoreKey("main")
keyXtra := sdk.NewKVStoreKey("xtra")
ms.MountStoreWithDB(keyMain, sdk.StoreTypeIAVL, dbMain)
ms.MountStoreWithDB(keyXtra, sdk.StoreTypeIAVL, dbXtra)
return map[string]sdk.StoreKey{
"main": keyMain,
"xtra": keyXtra,
}
}

View File

@ -8,7 +8,7 @@ import (
// Router provides handlers for each transaction type.
type Router interface {
AddRoute(r string, h sdk.Handler)
AddRoute(r string, h sdk.Handler) (rtr Router)
Route(path string) (h sdk.Handler)
}
@ -34,11 +34,13 @@ func NewRouter() *router {
var isAlpha = regexp.MustCompile(`^[a-zA-Z]+$`).MatchString
// AddRoute - TODO add description
func (rtr *router) AddRoute(r string, h sdk.Handler) {
func (rtr *router) AddRoute(r string, h sdk.Handler) Router {
if !isAlpha(r) {
panic("route expressions can only contain alphanumeric characters")
}
rtr.routes = append(rtr.routes, route{r, h})
return rtr
}
// Route - TODO add description

38
client/flags.go Normal file
View File

@ -0,0 +1,38 @@
package client
import "github.com/spf13/cobra"
// nolint
const (
FlagChainID = "chain-id"
FlagNode = "node"
FlagHeight = "height"
FlagTrustNode = "trust-node"
FlagName = "name"
)
// LineBreak can be included in a command list to provide a blank line
// to help with readability
var LineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
// GetCommands adds common flags to query commands
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
// TODO: make this default false when we support proofs
c.Flags().Bool(FlagTrustNode, true, "Don't verify proofs for responses")
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
c.Flags().Int64(FlagHeight, 0, "block height to query, omit to get most recent provable block")
}
return cmds
}
// PostCommands adds common flags for commands to post tx
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().String(FlagName, "", "Name of private key with which to sign")
c.Flags().String(FlagChainID, "", "Chain ID of tendermint node")
c.Flags().String(FlagNode, "tcp://localhost:46657", "<host>:<port> to tendermint rpc interface for this chain")
}
return cmds
}

71
client/helpers.go Normal file
View File

@ -0,0 +1,71 @@
package client
import (
"fmt"
"github.com/pkg/errors"
"github.com/spf13/viper"
rpcclient "github.com/tendermint/tendermint/rpc/client"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
cmn "github.com/tendermint/tmlibs/common"
)
// GetNode prepares a simple rpc.Client from the flags
func GetNode() (rpcclient.Client, error) {
uri := viper.GetString(FlagNode)
if uri == "" {
return nil, errors.New("Must define node using --node")
}
return rpcclient.NewHTTP(uri, "/websocket"), nil
}
// Broadcast the transaction bytes to Tendermint
func BroadcastTx(tx []byte) (*ctypes.ResultBroadcastTxCommit, error) {
node, err := 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
}
// Query from Tendermint with the provided key and storename
func Query(key cmn.HexBytes, storeName string) (res []byte, err error) {
path := fmt.Sprintf("/%s/key", storeName)
node, err := GetNode()
if err != nil {
return res, err
}
opts := rpcclient.ABCIQueryOptions{
Height: viper.GetInt64(FlagHeight),
Trusted: viper.GetBool(FlagTrustNode),
}
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
}

92
client/input.go Normal file
View File

@ -0,0 +1,92 @@
package client
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/bgentry/speakeasy"
isatty "github.com/mattn/go-isatty"
"github.com/pkg/errors"
)
// MinPassLength is the minimum acceptable password length
const MinPassLength = 8
// BufferStdin is used to allow reading prompts for stdin
// multiple times, when we read from non-tty
func BufferStdin() *bufio.Reader {
return bufio.NewReader(os.Stdin)
}
// GetPassword will prompt for a password one-time (to sign a tx)
// It enforces the password length
func GetPassword(prompt string, buf *bufio.Reader) (pass string, err error) {
if inputIsTty() {
pass, err = speakeasy.Ask(prompt)
} else {
pass, err = readLineFromBuf(buf)
}
if err != nil {
return "", err
}
if len(pass) < MinPassLength {
return "", errors.Errorf("Password must be at least %d characters", MinPassLength)
}
return pass, nil
}
// GetSeed will request a seed phrase from stdin and trims off
// leading/trailing spaces
func GetSeed(prompt string, buf *bufio.Reader) (seed string, err error) {
if inputIsTty() {
fmt.Println(prompt)
}
seed, err = readLineFromBuf(buf)
seed = strings.TrimSpace(seed)
return
}
// GetCheckPassword will prompt for a password twice to verify they
// match (for creating a new password).
// It enforces the password length. Only parses password once if
// input is piped in.
func GetCheckPassword(prompt, prompt2 string, buf *bufio.Reader) (string, error) {
// simple read on no-tty
if !inputIsTty() {
return GetPassword(prompt, buf)
}
// TODO: own function???
pass, err := GetPassword(prompt, buf)
if err != nil {
return "", err
}
pass2, err := GetPassword(prompt2, buf)
if err != nil {
return "", err
}
if pass != pass2 {
return "", errors.New("Passphrases don't match")
}
return pass, nil
}
// inputIsTty returns true iff we have an interactive prompt,
// where we can disable echo and request to repeat the password.
// If false, we can optimize for piped input from another command
func inputIsTty() bool {
return isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
}
// readLineFromBuf reads one line from stdin.
// Subsequent calls reuse the same buffer, so we don't lose
// any input when reading a password twice (to verify)
func readLineFromBuf(buf *bufio.Reader) (string, error) {
pass, err := buf.ReadString('\n')
if err != nil {
return "", err
}
return strings.TrimSpace(pass), nil
}

23
client/keys.go Normal file
View File

@ -0,0 +1,23 @@
package client
import (
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
dbm "github.com/tendermint/tmlibs/db"
)
// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase(db dbm.DB) keys.Keybase {
keybase := keys.New(
db,
words.MustLoadCodec("english"),
)
return keybase
}
// MockKeyBase generates an in-memory keybase that will be discarded
// useful for --dry-run to generate a seed phrase without
// storing the key
func MockKeyBase() keys.Keybase {
return GetKeyBase(dbm.NewMemDB())
}

119
client/keys/README.md Normal file
View File

@ -0,0 +1,119 @@
# Keys CLI
**WARNING: out-of-date and parts are wrong.... please update**
This is as much an example how to expose cobra/viper, as for a cli itself
(I think this code is overkill for what go-keys needs). But please look at
the commands, and give feedback and changes.
`RootCmd` calls some initialization functions (`cobra.OnInitialize` and `RootCmd.PersistentPreRunE`) which serve to connect environmental variables and cobra flags, as well as load the config file. It also validates the flags registered on root and creates the cryptomanager, which will be used by all subcommands.
## Help info
```
# keys help
Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.
Usage:
keys [command]
Available Commands:
get Get details of one key
list List all keys
new Create a new public/private key pair
serve Run the key manager as an http server
update Change the password for a private key
Flags:
--keydir string Directory to store private keys (subdir of root) (default "keys")
-o, --output string Output format (text|json) (default "text")
-r, --root string root directory for config and data (default "/Users/ethan/.tlc")
Use "keys [command] --help" for more information about a command.
```
## Getting the config file
The first step is to load in root, by checking the following in order:
* -r, --root command line flag
* TM_ROOT environmental variable
* default ($HOME/.tlc evaluated at runtime)
Once the `rootDir` is established, the script looks for a config file named `keys.{json,toml,yaml,hcl}` in that directory and parses it. These values will provide defaults for flags of the same name.
There is an example config file for testing out locally, which writes keys to `./.mykeys`. You can
## Getting/Setting variables
When we want to get the value of a user-defined variable (eg. `output`), we can call `viper.GetString("output")`, which will do the following checks, until it finds a match:
* Is `--output` command line flag present?
* Is `TM_OUTPUT` environmental variable set?
* Was a config file found and does it have an `output` variable?
* Is there a default set on the command line flag?
If no variable is set and there was no default, we get back "".
This setup allows us to have powerful command line flags, but use env variables or config files (local or 12-factor style) to avoid passing these arguments every time.
## Nesting structures
Sometimes we don't just need key-value pairs, but actually a multi-level config file, like
```
[mail]
from = "no-reply@example.com"
server = "mail.example.com"
port = 567
password = "XXXXXX"
```
This CLI is too simple to warant such a structure, but I think eg. tendermint could benefit from such an approach. Here are some pointers:
* [Accessing nested keys from config files](https://github.com/spf13/viper#accessing-nested-keys)
* [Overriding nested values with envvars](https://www.netlify.com/blog/2016/09/06/creating-a-microservice-boilerplate-in-go/#nested-config-values) - the mentioned outstanding PR is already merged into master!
* Overriding nested values with cli flags? (use `--log_config.level=info` ??)
I'd love to see an example of this fully worked out in a more complex CLI.
## Have your cake and eat it too
It's easy to render data different ways. Some better for viewing, some better for importing to other programs. You can just add some global (persistent) flags to control the output formatting, and everyone gets what they want.
```
# keys list -e hex
All keys:
betty d0789984492b1674e276b590d56b7ae077f81adc
john b77f4720b220d1411a649b6c7f1151eb6b1c226a
# keys list -e btc
All keys:
betty 3uTF4r29CbtnzsNHZoPSYsE4BDwH
john 3ZGp2Md35iw4XVtRvZDUaAEkCUZP
# keys list -e b64 -o json
[
{
"name": "betty",
"address": "0HiZhEkrFnTidrWQ1Wt64Hf4Gtw=",
"pubkey": {
"type": "secp256k1",
"data": "F83WvhT0KwttSoqQqd_0_r2ztUUaQix5EXdO8AZyREoV31Og780NW59HsqTAb2O4hZ-w-j0Z-4b2IjfdqqfhVQ=="
}
},
{
"name": "john",
"address": "t39HILIg0UEaZJtsfxFR62scImo=",
"pubkey": {
"type": "ed25519",
"data": "t1LFmbg_8UTwj-n1wkqmnTp6NfaOivokEhlYySlGYCY="
}
}
]
```

122
client/keys/add.go Normal file
View File

@ -0,0 +1,122 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/tmlibs/cli"
)
const (
flagType = "type"
flagRecover = "recover"
flagNoBackup = "no-backup"
flagDryRun = "dry-run"
)
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
Long: `Add a public/private key pair to the key store.
If you select --seed/-s you can recover a key from the seed
phrase, otherwise, a new key will be generated.`,
RunE: runAddCmd,
}
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
cmd.Flags().Bool(flagRecover, false, "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagNoBackup, false, "Don't print out seed phrase (if others are watching the terminal)")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
return cmd
}
func runAddCmd(cmd *cobra.Command, args []string) error {
var kb keys.Keybase
var err error
var name, pass string
buf := client.BufferStdin()
if viper.GetBool(flagDryRun) {
// we throw this away, so don't enforce args,
// we want to get a new random seed phrase quickly
kb = client.MockKeyBase()
pass = "throwing-this-key-away"
name = "inmemorykey"
} else {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name = args[0]
kb, err = GetKeyBase()
if err != nil {
return err
}
pass, err = client.GetCheckPassword(
"Enter a passphrase for your key:",
"Repeat the passphrase:", buf)
if err != nil {
return err
}
}
if viper.GetBool(flagRecover) {
seed, err := client.GetSeed(
"Enter your recovery seed phrase:", buf)
if err != nil {
return err
}
info, err := kb.Recover(name, pass, seed)
if err != nil {
return err
}
// print out results without the seed phrase
viper.Set(flagNoBackup, true)
printCreate(info, "")
} else {
algo := keys.CryptoAlgo(viper.GetString(flagType))
info, seed, err := kb.Create(name, pass, algo)
if err != nil {
return err
}
printCreate(info, seed)
}
return nil
}
// addOutput lets us json format the data
type addOutput struct {
Key keys.Info `json:"key"`
Seed string `json:"seed"`
}
func printCreate(info keys.Info, seed string) {
output := viper.Get(cli.OutputFlag)
switch output {
case "text":
printInfo(info)
// print seed unless requested not to.
if !viper.GetBool(flagNoBackup) {
fmt.Println("**Important** write this seed phrase in a safe place.")
fmt.Println("It is the only way to recover your account if you ever forget your password.")
fmt.Println()
fmt.Println(seed)
}
case "json":
out := addOutput{Key: info}
if !viper.GetBool(flagNoBackup) {
out.Seed = seed
}
json, err := MarshalJSON(out)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
default:
panic(fmt.Sprintf("I can't speak: %s", output))
}
}

45
client/keys/delete.go Normal file
View File

@ -0,0 +1,45 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func deleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete the given key",
RunE: runDeleteCmd,
}
return cmd
}
func runDeleteCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
buf := client.BufferStdin()
oldpass, err := client.GetPassword(
"DANGER - enter password to permanently delete key:", buf)
if err != nil {
return err
}
kb, err := GetKeyBase()
if err != nil {
return err
}
err = kb.Delete(name, oldpass)
if err != nil {
return err
}
fmt.Println("Password deleted forever (uh oh!)")
return nil
}

25
client/keys/list.go Normal file
View File

@ -0,0 +1,25 @@
package keys
import "github.com/spf13/cobra"
// listKeysCmd represents the list command
var listKeysCmd = &cobra.Command{
Use: "list",
Short: "List all keys",
Long: `Return a list of all public keys stored by this key manager
along with their associated name and address.`,
RunE: runListCmd,
}
func runListCmd(cmd *cobra.Command, args []string) error {
kb, err := GetKeyBase()
if err != nil {
return err
}
infos, err := kb.List()
if err == nil {
printInfos(infos)
}
return err
}

29
client/keys/root.go Normal file
View File

@ -0,0 +1,29 @@
package keys
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/cobra"
)
// Commands registers a sub-tree of commands to interact with
// local private key storage.
func Commands() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Add or view local private keys",
Long: `Keys allows you to manage your local keystore for tendermint.
These keys may be in any format supported by go-crypto and can be
used by light-clients, full nodes, or any other application that
needs to sign with a private key.`,
}
cmd.AddCommand(
addKeyCommand(),
listKeysCmd,
showKeysCmd,
client.LineBreak,
deleteKeyCommand(),
updateKeyCommand(),
)
return cmd
}

32
client/keys/show.go Normal file
View File

@ -0,0 +1,32 @@
package keys
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var showKeysCmd = &cobra.Command{
Use: "show <name>",
Short: "Show key info for the given name",
Long: `Return public details of one local key.`,
RunE: runShowCmd,
}
func runShowCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
kb, err := GetKeyBase()
if err != nil {
return err
}
info, err := kb.Get(name)
if err == nil {
printInfo(info)
}
return err
}

50
client/keys/update.go Normal file
View File

@ -0,0 +1,50 @@
package keys
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
func updateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",
RunE: runUpdateCmd,
}
return cmd
}
func runUpdateCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a name for the key")
}
name := args[0]
buf := client.BufferStdin()
oldpass, err := client.GetPassword(
"Enter the current passphrase:", buf)
if err != nil {
return err
}
newpass, err := client.GetCheckPassword(
"Enter the new passphrase:",
"Repeat the new passphrase:", buf)
if err != nil {
return err
}
kb, err := GetKeyBase()
if err != nil {
return err
}
err = kb.Update(name, oldpass, newpass)
if err != nil {
return err
}
fmt.Println("Password successfully updated!")
return nil
}

68
client/keys/utils.go Normal file
View File

@ -0,0 +1,68 @@
package keys
import (
"fmt"
"github.com/spf13/viper"
keys "github.com/tendermint/go-crypto/keys"
"github.com/tendermint/tmlibs/cli"
dbm "github.com/tendermint/tmlibs/db"
"github.com/cosmos/cosmos-sdk/client"
)
// KeyDBName is the directory under root where we store the keys
const KeyDBName = "keys"
var (
// keybase is used to make GetKeyBase a singleton
keybase keys.Keybase
)
// GetKeyBase initializes a keybase based on the configuration
func GetKeyBase() (keys.Keybase, error) {
if keybase == nil {
rootDir := viper.GetString(cli.HomeFlag)
db, err := dbm.NewGoLevelDB(KeyDBName, rootDir)
if err != nil {
return nil, err
}
keybase = client.GetKeyBase(db)
}
return keybase, nil
}
func printInfo(info keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":
addr := info.PubKey.Address().String()
sep := "\t\t"
if len(info.Name) > 7 {
sep = "\t"
}
fmt.Printf("%s%s%s\n", info.Name, sep, addr)
case "json":
json, err := MarshalJSON(info)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}
func printInfos(infos []keys.Info) {
switch viper.Get(cli.OutputFlag) {
case "text":
fmt.Println("All keys:")
for _, i := range infos {
printInfo(i)
}
case "json":
json, err := MarshalJSON(infos)
if err != nil {
panic(err) // really shouldn't happen...
}
fmt.Println(string(json))
}
}

17
client/keys/wire.go Normal file
View File

@ -0,0 +1,17 @@
package keys
import (
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var cdc *wire.Codec
func init() {
cdc = wire.NewCodec()
crypto.RegisterWire(cdc)
}
func MarshalJSON(o interface{}) ([]byte, error) {
return cdc.MarshalJSON(o)
}

36
client/lcd/root.go Normal file
View File

@ -0,0 +1,36 @@
package lcd
import (
"errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
)
const (
flagBind = "bind"
flagCORS = "cors"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// ServeCommand will generate a long-running rest server
// (aka Light Client Daemon) that exposes functionality similar
// to the cli, but over rest
func ServeCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}

66
client/rpc/block.go Normal file
View File

@ -0,0 +1,66 @@
package rpc
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
const (
flagSelect = "select"
)
func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block [height]",
Short: "Get verified data for a the block at given height",
RunE: getBlock,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
return cmd
}
func getBlock(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
// TODO: actually honor the --select flag!
// header -> BlockchainInfo
// header, tx -> Block
// results -> BlockResults
res, err := node.Block(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

44
client/rpc/root.go Normal file
View File

@ -0,0 +1,44 @@
package rpc
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
)
const (
// one of the following should be provided to verify the connection
flagGenesis = "genesis"
flagCommit = "commit"
flagValHash = "validator-set"
)
// XXX: remove this when not needed
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
// AddCommands adds a number of rpc-related subcommands
func AddCommands(cmd *cobra.Command) {
cmd.AddCommand(
initClientCommand(),
statusCommand(),
blockCommand(),
validatorCommand(),
)
}
func initClientCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize light client",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(client.FlagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}

40
client/rpc/status.go Normal file
View File

@ -0,0 +1,40 @@
package rpc
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
func statusCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: checkStatus,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
return cmd
}
func checkStatus(cmd *cobra.Command, args []string) error {
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
res, err := node.Status()
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

57
client/rpc/validators.go Normal file
View File

@ -0,0 +1,57 @@
package rpc
import (
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
tmwire "github.com/tendermint/tendermint/wire"
)
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: getValidators,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
return cmd
}
func getValidators(cmd *cobra.Command, args []string) error {
var height *int64
// optional height
if len(args) > 0 {
h, err := strconv.Atoi(args[0])
if err != nil {
return err
}
if h > 0 {
tmp := int64(h)
height = &tmp
}
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
res, err := node.Validators(height)
if err != nil {
return err
}
output, err := tmwire.MarshalJSON(res)
// output, err := json.MarshalIndent(res, " ", "")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

20
client/tx/root.go Normal file
View File

@ -0,0 +1,20 @@
package tx
import (
"github.com/spf13/cobra"
wire "github.com/tendermint/go-wire"
)
// type used to pass around the provided cdc
type commander struct {
cdc *wire.Codec
}
// AddCommands adds a number of tx-query related subcommands
func AddCommands(cmd *cobra.Command, cdc *wire.Codec) {
cmdr := commander{cdc}
cmd.AddCommand(
SearchTxCmd(cmdr),
QueryTxCmd(cmdr),
)
}

80
client/tx/search.go Normal file
View File

@ -0,0 +1,80 @@
package tx
import (
"errors"
"fmt"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
const (
flagTags = "tag"
flagAny = "any"
)
// default client command to search through tagged transactions
func SearchTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: cmdr.searchTxCmd,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false once proofs built in
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error {
tags := viper.GetStringSlice(flagTags)
if len(tags) == 0 {
return errors.New("Must declare at least one tag to search")
}
// XXX: implement ANY
query := strings.Join(tags, " AND ")
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.TxSearch(query, prove)
if err != nil {
return err
}
info, err := formatTxResults(c.cdc, res)
if err != nil {
return err
}
output, err := c.cdc.MarshalJSON(info)
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) {
var err error
out := make([]txInfo, len(res))
for i := range res {
out[i], err = formatTxResult(cdc, res[i])
if err != nil {
return nil, err
}
}
return out, nil
}

99
client/tx/tx.go Normal file
View File

@ -0,0 +1,99 @@
package tx
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
)
// Get the default command for a tx query
func QueryTxCmd(cmdr commander) *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash]",
Short: "Matches this txhash over all committed blocks",
RunE: cmdr.queryTxCmd,
}
cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to")
// TODO: change this to false when we can
cmd.Flags().Bool(client.FlagTrustNode, true, "Don't verify proofs for responses")
return cmd
}
// command to query for a transaction
func (c commander) queryTxCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide a tx hash")
}
// find the key to look up the account
hexStr := args[0]
hash, err := hex.DecodeString(hexStr)
if err != nil {
return err
}
// get the node
node, err := client.GetNode()
if err != nil {
return err
}
prove := !viper.GetBool(client.FlagTrustNode)
res, err := node.Tx(hash, prove)
if err != nil {
return err
}
info, err := formatTxResult(c.cdc, res)
if err != nil {
return err
}
output, err := json.MarshalIndent(info, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}
func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) {
// TODO: verify the proof if requested
tx, err := parseTx(cdc, res.Tx)
if err != nil {
return txInfo{}, err
}
info := txInfo{
Height: res.Height,
Tx: tx,
Result: res.TxResult,
}
return info, nil
}
// txInfo is used to prepare info to display
type txInfo struct {
Height int64 `json:"height"`
Tx sdk.Tx `json:"tx"`
Result abci.ResponseDeliverTx `json:"result"`
}
func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) {
var tx sdk.StdTx
err := cdc.UnmarshalBinary(txBytes, &tx)
if err != nil {
return nil, err
}
return tx, nil
}

View File

@ -17,10 +17,14 @@ stores they're given the key for. The BaseApp ensures all stores are properly lo
One mounted store is considered the "main" - it holds the latest block header, from which we can find and load the
most recent state.
BaseApp distinguishes between two handler types - the `AnteHandler` and the `Handler`.
The former is a stateful validity check (eg. checking nonce and sufficient balance),
the later the full state transition function. Only AnteHandler runs during CheckTx,
while both run in DeliverTx.
BaseApp distinguishes between two handler types - the `AnteHandler` and the `MsgHandler`.
The former is a global validity check (checking nonces, sigs and sufficient balances to pay fees,
e.g. things that apply to all transaction from all modules), the later is the full state transition function.
During CheckTx the state transition function is only applied to the checkTxState and should return
before any expensive state transitions are run (this is up to each developer). It also needs to return the estimated
gas cost.
During DeliverTx the state transition function is applied to the blockchain state and the transactions
need to be fully executed.
BaseApp is responsible for managing the context passed into handlers -
it makes the block header available and provides the right stores for CheckTx and DeliverTx.

View File

@ -282,7 +282,7 @@ func NewHandler(am sdk.AccountMapper) sdk.Handler {
### vs encoding/json
### vs protobuf
## Dummy example
## KVStore example
## Basecoin example

741
docs/sdk/lcd-rest-api.yaml Normal file
View File

@ -0,0 +1,741 @@
openapi: 3.0.0
servers:
- url: 'http://localhost:8998'
info:
version: "1.0.0-oas3"
title: Light client daemon to interface with Cosmos baseserver via REST
description: Specification for the LCD provided by `gaia rest-server`
paths:
/version:
get:
summary: Version of the light client daemon
description: Get the version of the LCD running locally to compare against expected
responses:
200:
description: Plaintext version i.e. "v0.5.0"
/node_info:
description: Only the node info. Block information can be queried via /block/latest
get:
summary: The propertied of the connected node
responses:
200:
description: Node status
content:
application/json:
schema:
type: object
properties:
pub_key:
$ref: '#/components/schemas/PubKey'
moniker:
type: string
example: 159.89.198.221
network:
type: string
example: gaia-2
remote_addr:
type: string
listen_addr:
type: string
example: 192.168.56.1:46656
version:
description: Tendermint version
type: string
example: 0.15.0
other:
description: more information on versions
type: array
/keys:
get:
summary: List of accounts stored locally
responses:
200:
description: Array of accounts
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Account'
post:
summary: Create a new account locally
responses:
200:
description: OK
requestBody:
content:
application/json:
schema:
type: object
required:
- name
- password
- seed
properties:
name:
type: string
password:
type: string
seed:
type: string
description: The account to create.
/keys/seed:
get:
summary: Create a new seed to create a new account with
responses:
200:
description: 12 word Seed
content:
application/json:
schema:
type: string
/keys/{name}:
parameters:
- in: path
name: name
description: Account name
required: true
schema:
type: string
get:
summary: Get a certain locally stored account
responses:
200:
description: Locally stored account
content:
application/json:
schema:
$ref: "#/components/schemas/Account"
404:
description: Account is not available
put:
summary: Update the password for this account
requestBody:
content:
application/json:
schema:
type: object
required:
- password
properties:
password:
type: string
responses:
200:
description: Updated password
401:
description: Password is wrong
404:
description: Account is not available
delete:
summary: Remove an account
requestBody:
content:
application/json:
schema:
type: object
required:
- password
properties:
password:
type: string
responses:
200:
description: Removed account
401:
description: Password is wrong
404:
description: Account is not available
/accounts/send:
post:
summary: Send coins (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
fees:
$ref: "#/components/schemas/Coins"
outputs:
type: array
items:
type: object
properties:
pub_key:
$ref: "#/components/schemas/PubKey"
amount:
type: array
items:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/accounts/{address}:
parameters:
- in: path
name: address
description: Account address
required: true
schema:
$ref: "#/components/schemas/Address"
get:
summary: Get the account balances
responses:
200:
description: Account balances
content:
application/json:
schema:
$ref: "#/components/schemas/Balance"
204:
description: There is no data for the requested account. This is not a 404 as the account might exist, just does not hold data.
/accounts/{address}/send:
parameters:
- in: path
name: address
description: Account address
required: true
schema:
$ref: "#/components/schemas/Address"
post:
summary: Send coins (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
fees:
$ref: "#/components/schemas/Coins"
amount:
type: array
items:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/accounts/{address}/nonce:
parameters:
- in: path
name: address
description: Account address
required: true
schema:
$ref: "#/components/schemas/Address"
get:
summary: Get the nonce for a certain account
responses:
200:
description: Plaintext nonce i.e. "4" defaults to "0"
/blocks/latest:
get:
summary: Get the latest block
responses:
200:
description: The latest block
content:
application/json:
schema:
$ref: "#/components/schemas/Block"
/blocks/{height}:
parameters:
- in: path
name: height
description: Block height
required: true
schema:
type: number
get:
summary: Get a block at a certain height
responses:
200:
description: The block at a specific height
content:
application/json:
schema:
$ref: "#/components/schemas/Block"
404:
description: Block at height is not available
/validatorsets/latest:
get:
summary: Get the latest validator set
responses:
200:
description: The validator set at the latest block height
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Delegate"
/validatorsets/{height}:
parameters:
- in: path
name: height
description: Block height
required: true
schema:
type: number
get:
summary: Get a validator set a certain height
responses:
200:
description: The validator set at a specific block height
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Delegate"
404:
description: Block at height not available
/txs:
parameters:
- in: query
name: tag
schema:
type: string
example: "coin.sender=EE5F3404034C524501629B56E0DDC38FAD651F04"
required: true
- in: query
name: page
description: Pagination page
schema:
type: number
default: 0
- in: query
name: size
description: Pagination size
schema:
type: number
default: 50
get:
summary: Query Tx
responses:
200:
description: All Tx matching the provided tags
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Tx"
404:
description: Pagination is out of bounds
/txs/sign:
post:
summary: Sign a Tx
description: Sign a Tx providing locally stored account and according password
security:
- sign: []
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/TxBuild"
responses:
200:
description: The signed Tx
content:
application/json:
schema:
$ref: "#/components/schemas/TxSigned"
401:
description: Account name and/or password where wrong
/txs/broadcast:
post:
summary: Send signed Tx
requestBody:
content:
application/json:
schema:
$ref: "#/components/schemas/TxSigned"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/txs/{hash}:
parameters:
- in: path
name: hash
description: Tx hash
required: true
schema:
$ref: "#/components/schemas/Hash"
get:
summary: Get a Tx by hash
responses:
200:
description: Tx with the provided hash
content:
application/json:
schema:
$ref: "#/components/schemas/Tx"
404:
description: Tx not available for provided hash
/delegates:
parameters:
- in: query
name: delegator
description: Query for all delegates a delegator has stake with
schema:
$ref: "#/components/schemas/Address"
get:
summary: Get a list of canidates/delegates/validators (optionally filtered by delegator)
responses:
200:
description: List of delegates, filtered by provided delegator address
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Delegate"
/delegates/bond:
post:
summary: Bond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: array
items:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
pub_key:
$ref: "#/components/schemas/PubKey"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/unbond:
post:
summary: Unbond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: array
items:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
pub_key:
$ref: "#/components/schemas/PubKey"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/{pubkey}:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
get:
summary: Get a certain canidate/delegate/validator
responses:
200:
description: Delegate for specified pub_key
content:
application/json:
schema:
$ref: "#/components/schemas/Delegate"
404:
description: No delegate found for provided pub_key
/delegates/{pubkey}/bond:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
post:
summary: Bond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
/delegates/{pubkey}/unbond:
parameters:
- in: path
name: pubkey
description: Pubkey of a delegate
required: true
schema:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
post:
summary: Unbond atoms (build -> sign -> send)
security:
- sign: []
requestBody:
content:
application/json:
schema:
type: object
properties:
amount:
$ref: "#/components/schemas/Coins"
responses:
202:
description: Tx was send and will probably be added to the next block
400:
description: The Tx was malformated
components:
schemas:
Address:
type: string
example: DF096FDE8D380FA5B2AD20DB2962C82DDEA1ED9B
Coins:
type: object
properties:
denom:
type: string
example: fermion
amount:
type: number
example: 50
Hash:
type: string
example: EE5F3404034C524501629B56E0DDC38FAD651F04
Tx:
type: object
properties:
type:
type: string
enum:
- stake/delegate
data:
type: object
TxChain:
type: object
properties:
type:
type: string
default: chain/tx
data:
type: object
properties:
chain_id:
type: string
example: gaia-2
expires_at:
type: number
example: 0
tx:
type: object
properties:
type:
type: string
default: nonce
data:
type: object
properties:
sequence:
type: number
example: 0
signers:
type: array
items:
type: object
properties:
chain:
type: string
example: ''
app:
type: string
default: sigs
addr:
$ref: "#/components/schemas/Address"
tx:
$ref: "#/components/schemas/Tx"
TxBuild:
type: object
properties:
type:
type: string
default: sigs/one
data:
type: object
properties:
tx:
$ref: "#/components/schemas/Tx"
signature:
type: object
properties:
Sig:
type: string
default: ''
Pubkey:
type: string
default: ''
TxSigned:
type: object
properties:
type:
type: string
default: sigs/one
data:
type: object
properties:
tx:
$ref: "#/components/schemas/Tx"
signature:
type: object
properties:
Sig:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
Pubkey:
$ref: "#/components/schemas/PubKey"
PubKey:
type: object
properties:
type:
type: string
enum:
- ed25519
data:
type: string
example: 81B11E717789600CC192B26F452A983DF13B985EE75ABD9DD9E68D7BA007A958
Account:
type: object
properties:
name:
type: string
example: Main Account
address:
$ref: "#/components/schemas/Address"
pub_key:
$ref: "#/components/schemas/PubKey"
Balance:
type: object
properties:
height:
type: number
example: 123456
coins:
type: array
items:
$ref: "#/components/schemas/Coins"
credit:
type: array
BlockID:
type: object
properties:
hash:
$ref: "#/components/schemas/Hash"
parts:
type: object
properties:
total:
type: number
example: 0
hash:
$ref: "#/components/schemas/Hash"
Block:
type: object
properties:
header:
type: object
properties:
chain_id:
type: string
example: gaia-2
height:
type: number
example: 1
time:
type: string
example: '2017-12-30T05:53:09.287+01:00'
num_txs:
type: number
example: 0
last_block_id:
$ref: "#/components/schemas/BlockID"
total_txs:
type: number
example: 35
last_commit_hash:
$ref: "#/components/schemas/Hash"
data_hash:
$ref: "#/components/schemas/Hash"
validators_hash:
$ref: "#/components/schemas/Hash"
consensus_hash:
$ref: "#/components/schemas/Hash"
app_hash:
$ref: "#/components/schemas/Hash"
last_results_hash:
$ref: "#/components/schemas/Hash"
evidence_hash:
$ref: "#/components/schemas/Hash"
txs:
type: array
items:
$ref: "#/components/schemas/Tx"
evidence:
type: array
last_commit:
type: object
properties:
blockID:
$ref: "#/components/schemas/BlockID"
precommits:
type: array
Delegate:
type: object
properties:
pub_key:
$ref: "#/components/schemas/PubKey"
power:
type: number
example: 1000
name:
type: string
example: "159.89.3.34"
securitySchemes:
sign:
type: http
scheme: basic

View File

@ -421,8 +421,8 @@ vs encoding/json
vs protobuf
~~~~~~~~~~~
Dummy example
-------------
KVStore example
---------------
Basecoin example
----------------

View File

@ -1,19 +1,15 @@
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
BUILD_FLAGS = -ldflags "-X github.com/cosmos/cosmos-sdk/examples/basecoin/version.GitCommit=`git rev-parse --short HEAD`"
all: get_vendor_deps build test
all: build test
build:
go build $(BUILD_FLAGS) -o build/basecoin ./cmd/...
get_vendor_deps:
@rm -rf vendor/
@glide install
test:
@go test $(PACKAGES)
benchmark:
@go test -bench=. $(PACKAGES)
.PHONY: build get_vendor_deps test benchmark
.PHONY: all build test benchmark

View File

@ -2,18 +2,7 @@ This is the "Basecoin" example application built on the Cosmos-SDK. This
"Basecoin" is not affiliated with [Coinbase](http://www.getbasecoin.com/), nor
the [stable coin](http://www.getbasecoin.com/).
You need a recent version of `glide` to install Basecoin's dependencies.
```bash
> make get_tools
```
Then, you can build the cmd binaries (NOTE: a work in progress!), or run the tests.
```
> make get_vendor_deps
> make build
> make test
```
Assuming you've run `make get_tools && make get_vendor_deps` from the root of this repository,
run `make build` here to build the `basecoind` and `basecli` binaries.
If you want to create a new application, start by copying the Basecoin app.

View File

@ -40,7 +40,7 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// create your application object
var app = &BasecoinApp{
BaseApp: bam.NewBaseApp(appName, logger, db),
cdc: MakeTxCodec(),
cdc: MakeCodec(),
capKeyMainStore: sdk.NewKVStoreKey("main"),
capKeyIBCStore: sdk.NewKVStoreKey("ibc"),
}
@ -53,13 +53,16 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
// add handlers
coinKeeper := bank.NewCoinKeeper(app.accountMapper)
app.Router().AddRoute("bank", bank.NewHandler(coinKeeper))
app.Router().AddRoute("sketchy", sketchy.NewHandler())
app.Router().
AddRoute("bank", bank.NewHandler(coinKeeper)).
AddRoute("sketchy", sketchy.NewHandler())
// initialize BaseApp
app.SetTxDecoder(app.txDecoder)
app.SetInitChainer(app.initChainer)
app.MountStoresIAVL(app.capKeyMainStore, app.capKeyIBCStore)
// TODO: mounting multiple stores is broken
// https://github.com/cosmos/cosmos-sdk/issues/532
app.MountStoresIAVL(app.capKeyMainStore) // , app.capKeyIBCStore)
app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper))
err := app.LoadLatestVersion(app.capKeyMainStore)
if err != nil {
@ -70,10 +73,11 @@ func NewBasecoinApp(logger log.Logger, db dbm.DB) *BasecoinApp {
}
// custom tx codec
func MakeTxCodec() *wire.Codec {
func MakeCodec() *wire.Codec {
cdc := wire.NewCodec()
crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
bank.RegisterWire(cdc) // Register bank.[SendMsg,IssueMsg] types.
crypto.RegisterWire(cdc) // Register crypto.[PubKey,PrivKey,Signature] types.
return cdc
}

View File

@ -53,18 +53,27 @@ func TestSendMsg(t *testing.T) {
Signature: sig,
}})
// just marshal/unmarshal!
cdc := MakeCodec()
txBytes, err := cdc.MarshalBinary(tx)
require.NoError(t, err)
// Run a Check
res := bapp.Check(tx)
assert.Equal(t, sdk.CodeUnrecognizedAddress, res.Code, res.Log)
cres := bapp.CheckTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(cres.Code), cres.Log)
// Simulate a Block
bapp.BeginBlock(abci.RequestBeginBlock{})
res = bapp.Deliver(tx)
assert.Equal(t, sdk.CodeUnrecognizedAddress, res.Code, res.Log)
dres := bapp.DeliverTx(txBytes)
assert.Equal(t, sdk.CodeUnrecognizedAddress,
sdk.CodeType(dres.Code), dres.Log)
}
func TestGenesis(t *testing.T) {
bapp := newBasecoinApp()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app")
db := dbm.NewMemDB()
bapp := NewBasecoinApp(logger, db)
// Construct some genesis bytes to reflect basecoin/types/AppAccount
pk := crypto.GenPrivKeyEd25519().PubKey()
@ -86,12 +95,19 @@ func TestGenesis(t *testing.T) {
vals := []abci.Validator{}
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
bapp.Commit()
// A checkTx context
ctx := bapp.BaseApp.NewContext(true, abci.Header{})
res1 := bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)
// reload app and ensure the account is still there
bapp = NewBasecoinApp(logger, db)
ctx = bapp.BaseApp.NewContext(true, abci.Header{})
res1 = bapp.accountMapper.GetAccount(ctx, baseAcc.Address)
assert.Equal(t, acc, res1)
}
func TestSendMsgWithAccounts(t *testing.T) {
@ -127,6 +143,7 @@ func TestSendMsgWithAccounts(t *testing.T) {
// Initialize the chain
vals := []abci.Validator{}
bapp.InitChain(abci.RequestInitChain{vals, stateBytes})
bapp.Commit()
// A checkTx context (true)
ctxCheck := bapp.BaseApp.NewContext(true, abci.Header{})

View File

@ -0,0 +1,71 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/client/lcd"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/version"
authcmd "github.com/cosmos/cosmos-sdk/x/auth/commands"
bankcmd "github.com/cosmos/cosmos-sdk/x/bank/commands"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/examples/basecoin/types"
)
// gaiacliCmd is the entry point for this binary
var (
basecliCmd = &cobra.Command{
Use: "basecli",
Short: "Basecoin light-client",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// get the codec
cdc := app.MakeCodec()
// add standard rpc, and tx commands
rpc.AddCommands(basecliCmd)
basecliCmd.AddCommand(client.LineBreak)
tx.AddCommands(basecliCmd, cdc)
basecliCmd.AddCommand(client.LineBreak)
// add query/post commands (custom to binary)
basecliCmd.AddCommand(
client.GetCommands(
authcmd.GetAccountCmd("main", cdc, types.GetParseAccount(cdc)),
)...)
basecliCmd.AddCommand(
client.PostCommands(
bankcmd.SendTxCmd(cdc),
)...)
// add proxy, version and key info
basecliCmd.AddCommand(
client.LineBreak,
lcd.ServeCommand(),
keys.Commands(),
client.LineBreak,
version.VersionCmd,
)
// prepare and add flags
executor := cli.PrepareMainCmd(basecliCmd, "BC", os.ExpandEnv("$HOME/.basecli"))
executor.Execute()
}

View File

@ -1,27 +1,76 @@
package main
import (
"encoding/json"
"fmt"
"os"
"github.com/spf13/cobra"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/cli"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/examples/basecoin/app"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
)
func main() {
fmt.Println("This is temporary, for unblocking our build process.")
return
// basecoindCmd is the entry point for this binary
var (
basecoindCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
// TODO CREATE CLI
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
db, err := dbm.NewGoLevelDB("basecoind", "data")
// defaultOptions sets up the app_options for the
// default genesis file
func defaultOptions(args []string) (json.RawMessage, error) {
addr, secret, err := server.GenerateCoinKey()
if err != nil {
fmt.Println(err)
os.Exit(1)
return nil, err
}
fmt.Println("Secret phrase to access coins:")
fmt.Println(secret)
opts := fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}]
}`, addr)
return json.RawMessage(opts), nil
}
func generateApp(rootDir string, logger log.Logger) (abci.Application, error) {
db, err := dbm.NewGoLevelDB("basecoin", rootDir)
if err != nil {
return nil, err
}
bapp := app.NewBasecoinApp(logger, db)
baseapp.RunForever(bapp)
return bapp, nil
}
func main() {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "main")
basecoindCmd.AddCommand(
server.InitCmd(defaultOptions, logger),
server.StartCmd(generateApp, logger),
server.UnsafeResetAllCmd(logger),
version.VersionCmd,
)
// prepare and add flags
rootDir := os.ExpandEnv("$HOME/.basecoind")
executor := cli.PrepareBaseCmd(basecoindCmd, "BC", rootDir)
executor.Execute()
}

View File

@ -1,4 +0,0 @@
package: github.com/cosmos/cosmos-sdk/examples/basecoin
import:
- package: github.com/cosmos/cosmos-sdk
version: develop

View File

@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
)
var _ sdk.Account = (*AppAccount)(nil)
@ -15,13 +16,22 @@ var _ sdk.Account = (*AppAccount)(nil)
// auth.AccountStore uses the flexible go-wire library.
type AppAccount struct {
auth.BaseAccount
Name string
Name string `json:"name"`
}
// nolint
func (acc AppAccount) GetName() string { return acc.Name }
func (acc *AppAccount) SetName(name string) { acc.Name = name }
// Get the ParseAccount function for the custom AppAccount
func GetParseAccount(cdc *wire.Codec) sdk.ParseAccount {
return func(accBytes []byte) (res sdk.Account, err error) {
acct := new(AppAccount)
err = cdc.UnmarshalBinary(accBytes, acct)
return acct, err
}
}
//___________________________________________________________________________________
// State to Unmarshal

View File

@ -1,131 +0,0 @@
package main
import "github.com/spf13/cobra"
const (
// these are needed for every init
flagChainID = "chain-id"
flagNode = "node"
// one of the following should be provided to verify the connection
flagGenesis = "genesis"
flagCommit = "commit"
flagValHash = "validator-set"
flagSelect = "select"
flagTags = "tag"
flagAny = "any"
flagBind = "bind"
flagCORS = "cors"
flagTrustNode = "trust-node"
// this is for signing
flagName = "name"
)
var (
statusCmd = &cobra.Command{
Use: "status",
Short: "Query remote node for status",
RunE: todoNotImplemented,
}
)
// AddClientCommands returns a sub-tree of all basic client commands
//
// Call AddGetCommand and AddPostCommand to add custom txs and queries
func AddClientCommands(cmd *cobra.Command) {
cmd.AddCommand(
initClientCommand(),
statusCmd,
blockCommand(),
validatorCommand(),
lineBreak,
txSearchCommand(),
txCommand(),
lineBreak,
)
}
// GetCommands adds common flags to query commands
func GetCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().Bool(flagTrustNode, false, "Don't verify proofs for responses")
}
return cmds
}
// PostCommands adds common flags for commands to post tx
func PostCommands(cmds ...*cobra.Command) []*cobra.Command {
for _, c := range cmds {
c.Flags().String(flagName, "", "Name of private key with which to sign")
c.Flags().String(flagPassword, "", "Password to use the named private key")
}
return cmds
}
func initClientCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "init",
Short: "Initialize light client",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagChainID, "c", "", "ID of chain we connect to")
cmd.Flags().StringP(flagNode, "n", "tcp://localhost:46657", "Node to connect to")
cmd.Flags().String(flagGenesis, "", "Genesis file to verify header validity")
cmd.Flags().String(flagCommit, "", "File with trusted and signed header")
cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)")
return cmd
}
func blockCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "block <height>",
Short: "Get verified data for a the block at given height",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagSelect, []string{"header", "tx"}, "Fields to return (header|txs|results)")
return cmd
}
func validatorCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "validatorset <height>",
Short: "Get the full validator set at given height",
RunE: todoNotImplemented,
}
return cmd
}
func serveCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "serve",
Short: "Start LCD (light-client daemon), a local REST server",
RunE: todoNotImplemented,
}
// TODO: handle unix sockets also?
cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to")
cmd.Flags().String(flagCORS, "", "Set to domains that can make CORS requests (* for all)")
return cmd
}
func txSearchCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "txs",
Short: "Search for all transactions that match the given tags",
RunE: todoNotImplemented,
}
cmd.Flags().StringSlice(flagTags, nil, "Tags that must match (may provide multiple)")
cmd.Flags().Bool(flagAny, false, "Return transactions that match ANY tag, rather than ALL")
return cmd
}
func txCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "tx <hash>",
Short: "Matches this txhash over all committed blocks",
RunE: todoNotImplemented,
}
return cmd
}

View File

@ -1,77 +0,0 @@
package main
import "github.com/spf13/cobra"
const (
flagPassword = "password"
flagNewPassword = "new-password"
flagType = "type"
flagSeed = "seed"
flagDryRun = "dry-run"
)
var (
listKeysCmd = &cobra.Command{
Use: "list",
Short: "List all locally availably keys",
RunE: todoNotImplemented,
}
showKeysCmd = &cobra.Command{
Use: "show <name>",
Short: "Show key info for the given name",
RunE: todoNotImplemented,
}
)
// KeyCommands registers a sub-tree of commands to interact with
// local private key storage.
func KeyCommands() *cobra.Command {
cmd := &cobra.Command{
Use: "keys",
Short: "Add or view local private keys",
}
cmd.AddCommand(
addKeyCommand(),
listKeysCmd,
showKeysCmd,
lineBreak,
deleteKeyCommand(),
updateKeyCommand(),
)
return cmd
}
func addKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "add <name>",
Short: "Create a new key, or import from seed",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password to encrypt private key")
cmd.Flags().StringP(flagType, "t", "ed25519", "Type of private key (ed25519|secp256k1|ledger)")
cmd.Flags().StringP(flagSeed, "s", "", "Provide seed phrase to recover existing key instead of creating")
cmd.Flags().Bool(flagDryRun, false, "Perform action, but don't add key to local keystore")
return cmd
}
func updateKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "update <name>",
Short: "Change the password used to protect private key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Current password to decrypt key")
cmd.Flags().String(flagNewPassword, "", "New password to use to protect key")
return cmd
}
func deleteKeyCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "delete <name>",
Short: "Delete the given key",
RunE: todoNotImplemented,
}
cmd.Flags().StringP(flagPassword, "p", "", "Password of existing key to delete")
return cmd
}

View File

@ -1,75 +0,0 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
)
// gaiacliCmd is the entry point for this binary
var (
gaiacliCmd = &cobra.Command{
Use: "gaiacli",
Short: "Gaia light-client",
}
lineBreak = &cobra.Command{Run: func(*cobra.Command, []string) {}}
getAccountCmd = &cobra.Command{
Use: "account <address>",
Short: "Query account balance",
RunE: todoNotImplemented,
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func postSendCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "send",
Short: "Create and sign a send tx",
RunE: todoNotImplemented,
}
cmd.Flags().String(flagTo, "", "Address to send coins")
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
cmd.Flags().String(flagFee, "", "Fee to pay along with transaction")
return cmd
}
func main() {
// disable sorting
cobra.EnableCommandSorting = false
// generic client commands
AddClientCommands(gaiacliCmd)
// query commands (custom to binary)
gaiacliCmd.AddCommand(
GetCommands(getAccountCmd)...)
// post tx commands (custom to binary)
gaiacliCmd.AddCommand(
PostCommands(postSendCommand())...)
// add proxy, version and key info
gaiacliCmd.AddCommand(
lineBreak,
serveCommand(),
KeyCommands(),
lineBreak,
VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiacliCmd, "GA", os.ExpandEnv("$HOME/.gaiacli"))
executor.Execute()
}

View File

@ -1,44 +0,0 @@
package main
import (
"errors"
"os"
"github.com/spf13/cobra"
"github.com/tendermint/tmlibs/cli"
"github.com/cosmos/cosmos-sdk/baseapp"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
)
// gaiadCmd is the entry point for this binary
var (
gaiadCmd = &cobra.Command{
Use: "gaiad",
Short: "Gaia Daemon (server)",
}
)
func todoNotImplemented(_ *cobra.Command, _ []string) error {
return errors.New("TODO: Command not yet implemented")
}
func main() {
// TODO: set this to something real
var node baseapp.BaseApp
AddNodeCommands(gaiadCmd, node)
gaiadCmd.AddCommand(
VersionCmd,
)
// prepare and add flags
executor := cli.PrepareBaseCmd(gaiadCmd, "GA", os.ExpandEnv("$HOME/.gaiad"))
executor.Execute()
}

View File

@ -1,47 +0,0 @@
package main
import (
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/baseapp"
)
const (
flagWithTendermint = "with-tendermint"
)
var (
initNodeCmd = &cobra.Command{
Use: "init <flags???>",
Short: "Initialize full node",
RunE: todoNotImplemented,
}
resetNodeCmd = &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset full node data (danger, must resync)",
RunE: todoNotImplemented,
}
)
// AddNodeCommands registers all commands to interact
// with a local full-node as subcommands of the argument.
//
// Accept an application it should start
func AddNodeCommands(cmd *cobra.Command, node baseapp.BaseApp) {
cmd.AddCommand(
initNodeCmd,
startNodeCmd(node),
resetNodeCmd,
)
}
func startNodeCmd(node baseapp.BaseApp) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
RunE: todoNotImplemented,
}
cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint")
return cmd
}

View File

@ -1,26 +0,0 @@
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/version"
)
var (
// VersionCmd prints out the current sdk version
VersionCmd = &cobra.Command{
Use: "version",
Short: "Print the app version",
Run: doVersionCmd,
}
)
func doVersionCmd(cmd *cobra.Command, args []string) {
v := version.Version
if version.GitCommit != "" {
v = v + " " + version.GitCommit
}
fmt.Println(v)
}

View File

@ -27,7 +27,7 @@ func main() {
var capKeyMainStore = sdk.NewKVStoreKey("main")
// Create BaseApp.
var baseApp = bam.NewBaseApp("dummy", logger, db)
var baseApp = bam.NewBaseApp("kvstore", logger, db)
// Set mounts for BaseApp's MultiStore.
baseApp.MountStore(capKeyMainStore, sdk.StoreTypeIAVL)
@ -36,7 +36,7 @@ func main() {
baseApp.SetTxDecoder(decodeTx)
// Set a handler Route.
baseApp.Router().AddRoute("dummy", DummyHandler(capKeyMainStore))
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore))
// Load latest version.
if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil {
@ -60,11 +60,11 @@ func main() {
return
}
func DummyHandler(storeKey sdk.StoreKey) sdk.Handler {
func KVStoreHandler(storeKey sdk.StoreKey) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
dTx, ok := msg.(dummyTx)
dTx, ok := msg.(kvstoreTx)
if !ok {
panic("DummyHandler should only receive dummyTx")
panic("KVStoreHandler should only receive kvstoreTx")
}
// tx is already unmarshalled

View File

@ -8,13 +8,13 @@ import (
)
// An sdk.Tx which is its own sdk.Msg.
type dummyTx struct {
type kvstoreTx struct {
key []byte
value []byte
bytes []byte
}
func (tx dummyTx) Get(key interface{}) (value interface{}) {
func (tx kvstoreTx) Get(key interface{}) (value interface{}) {
switch k := key.(type) {
case string:
switch k {
@ -27,32 +27,32 @@ func (tx dummyTx) Get(key interface{}) (value interface{}) {
return nil
}
func (tx dummyTx) Type() string {
return "dummy"
func (tx kvstoreTx) Type() string {
return "kvstore"
}
func (tx dummyTx) GetMsg() sdk.Msg {
func (tx kvstoreTx) GetMsg() sdk.Msg {
return tx
}
func (tx dummyTx) GetSignBytes() []byte {
func (tx kvstoreTx) GetSignBytes() []byte {
return tx.bytes
}
// Should the app be calling this? Or only handlers?
func (tx dummyTx) ValidateBasic() sdk.Error {
func (tx kvstoreTx) ValidateBasic() sdk.Error {
return nil
}
func (tx dummyTx) GetSigners() []crypto.Address {
func (tx kvstoreTx) GetSigners() []crypto.Address {
return nil
}
func (tx dummyTx) GetSignatures() []sdk.StdSignature {
func (tx kvstoreTx) GetSignatures() []sdk.StdSignature {
return nil
}
func (tx dummyTx) GetFeePayer() crypto.Address {
func (tx kvstoreTx) GetFeePayer() crypto.Address {
return nil
}
@ -64,10 +64,10 @@ func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
split := bytes.Split(txBytes, []byte("="))
if len(split) == 1 {
k := split[0]
tx = dummyTx{k, k, txBytes}
tx = kvstoreTx{k, k, txBytes}
} else if len(split) == 2 {
k, v := split[0], split[1]
tx = dummyTx{k, v, txBytes}
tx = kvstoreTx{k, v, txBytes}
} else {
return nil, sdk.ErrTxParse("too many =")
}

76
glide.lock generated
View File

@ -1,6 +1,8 @@
hash: 2b4ad3bf1489a7cb5e62c6cb4c1fa976d4ae21993743e4968418c4e81925fb99
updated: 2018-02-19T17:13:04.368106064Z
hash: fa45c8a4f5512ed730f793b93d4876bdc604a1333a5a1f938c98a0f7dd55f22e
updated: 2018-03-01T00:41:12.97082395-05:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: 50de9da05b50eb15658bb350f6ea24368a111ab7
subpackages:
@ -9,6 +11,8 @@ imports:
version: 346938d642f2ec3594ed81d874461961cd0faa76
subpackages:
- spew
- name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
- name: github.com/fsnotify/fsnotify
version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9
- name: github.com/go-kit/kit
@ -40,6 +44,8 @@ imports:
- ptypes/timestamp
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/gorilla/websocket
version: ea4d1f681babbce9545c9c5f3d5194a789c89f5b
- name: github.com/hashicorp/hcl
version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8
subpackages:
@ -51,6 +57,8 @@ imports:
- json/parser
- json/scanner
- json/token
- name: github.com/howeyc/crc16
version: 96a97a1abb579c7ff1a8ffa77f2e72d1c314b57f
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo
@ -59,12 +67,16 @@ imports:
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 49d762b9817ba1c2e9d0c69183c2b4a8b8f1d934
- name: github.com/mattn/go-isatty
version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39
- name: github.com/mitchellh/mapstructure
version: b4575eea38cca1123ec2dc90c26529b5c5acfcff
- name: github.com/pelletier/go-toml
version: acdc4509485b587f5e675510c4f2c63e90ff68a8
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/rcrowley/go-metrics
version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/spf13/afero
@ -97,8 +109,11 @@ imports:
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: c960c5275617ef141c92c3d7fc65a396c97662df
version: 68592f4d8ee34e97db94b7a7976b1309efdb7eb9
subpackages:
- client
- example/code
- example/dummy
- server
- types
- name: github.com/tendermint/ed25519
@ -108,21 +123,72 @@ imports:
- extra25519
- name: github.com/tendermint/go-crypto
version: 4fc3055dbd17aa1203d0abc64b9293f378da22ec
subpackages:
- keys
- keys/bcrypt
- keys/words
- keys/words/wordlist
- name: github.com/tendermint/go-wire
version: 5d7845f24b843c914cf571dad2ca13c91cf70f0d
- name: github.com/tendermint/iavl
version: 1a59ec0c82dc940c25339dd7c834df5cb76a95cb
- name: github.com/tendermint/tmlibs
version: a0f652dc2e131be86fc8d9e4e2beec9831a8a6ec
- name: github.com/tendermint/tendermint
version: c330b9e43c93351a5c3040333d7d0c7c27859a20
subpackages:
- blockchain
- cmd/tendermint/commands
- config
- consensus
- consensus/types
- evidence
- lite
- lite/client
- lite/errors
- lite/files
- lite/proxy
- mempool
- node
- p2p
- p2p/conn
- p2p/pex
- p2p/trust
- p2p/upnp
- proxy
- rpc/client
- rpc/core
- rpc/core/types
- rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state
- state/txindex
- state/txindex/kv
- state/txindex/null
- types
- version
- wire
- name: github.com/tendermint/tmlibs
version: 26f2ab65f82cfc6873c312e8030104c47c05f10e
subpackages:
- autofile
- cli
- cli/flags
- clist
- common
- db
- flowrate
- log
- merkle
- pubsub
- pubsub/query
- name: golang.org/x/crypto
version: 1875d0a70c90e57f11972aefd42276df65e895b9
subpackages:
- blowfish
- curve25519
- nacl/box
- nacl/secretbox
- openpgp/armor
- openpgp/errors

View File

@ -4,6 +4,10 @@ import:
version: ^1.0.0
subpackages:
- proto
- package: github.com/bgentry/speakeasy
version: ^0.1.0
- package: github.com/mattn/go-isatty
version: ~0.0.3
- package: github.com/pkg/errors
version: ^0.8.0
- package: github.com/rigelrozanski/common
@ -25,6 +29,14 @@ import:
- db
- log
- merkle
- package: github.com/tendermint/tendermint
version: breaking/wire-sdk2
subpackages:
- cmd/tendermint/commands
- config
- lite
- rpc/client
- types
- package: golang.org/x/crypto
subpackages:
- ripemd160

122
mock/app.go Normal file
View File

@ -0,0 +1,122 @@
package mock
import (
"encoding/json"
"fmt"
abci "github.com/tendermint/abci/types"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
bam "github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// NewApp creates a simple mock kvstore app for testing.
// It should work similar to a real app.
// Make sure rootDir is empty before running the test,
// in order to guarantee consistent results
func NewApp(rootDir string, logger log.Logger) (abci.Application, error) {
db, err := dbm.NewGoLevelDB("mock", rootDir)
if err != nil {
return nil, err
}
// Capabilities key to access the main KVStore.
capKeyMainStore := sdk.NewKVStoreKey("main")
// Create BaseApp.
baseApp := bam.NewBaseApp("kvstore", logger, db)
// Set mounts for BaseApp's MultiStore.
baseApp.MountStoresIAVL(capKeyMainStore)
// Set Tx decoder
baseApp.SetTxDecoder(decodeTx)
baseApp.SetInitChainer(InitChainer(capKeyMainStore))
// Set a handler Route.
baseApp.Router().AddRoute("kvstore", KVStoreHandler(capKeyMainStore))
// Load latest version.
if err := baseApp.LoadLatestVersion(capKeyMainStore); err != nil {
return nil, err
}
return baseApp, nil
}
// KVStoreHandler is a simple handler that takes kvstoreTx and writes
// them to the db
func KVStoreHandler(storeKey sdk.StoreKey) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
dTx, ok := msg.(kvstoreTx)
if !ok {
panic("KVStoreHandler should only receive kvstoreTx")
}
// tx is already unmarshalled
key := dTx.key
value := dTx.value
store := ctx.KVStore(storeKey)
store.Set(key, value)
return sdk.Result{
Code: 0,
Log: fmt.Sprintf("set %s=%s", key, value),
}
}
}
// basic KV structure
type KV struct {
Key string `json:"key"`
Value string `json:"value"`
}
// What Genesis JSON is formatted as
type GenesisJSON struct {
Values []KV `json:"values"`
}
// InitChainer returns a function that can initialize the chain
// with key/value pairs
func InitChainer(key sdk.StoreKey) func(sdk.Context, abci.RequestInitChain) abci.ResponseInitChain {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes
genesisState := new(GenesisJSON)
err := json.Unmarshal(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
}
for _, val := range genesisState.Values {
store := ctx.KVStore(key)
store.Set([]byte(val.Key), []byte(val.Value))
}
return abci.ResponseInitChain{}
}
}
// GenInitOptions can be passed into InitCmd,
// returns a static string of a few key-values that can be parsed
// by InitChainer
func GenInitOptions(args []string) (json.RawMessage, error) {
opts := []byte(`{
"values": [
{
"key": "hello",
"value": "goodbye"
},
{
"key": "foo",
"value": "bar"
}
]
}`)
return opts, nil
}

75
mock/app_test.go Normal file
View File

@ -0,0 +1,75 @@
package mock
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
)
// TestInitApp makes sure we can initialize this thing without an error
func TestInitApp(t *testing.T) {
// set up an app
app, closer, err := SetupApp()
// closer may need to be run, even when error in later stage
if closer != nil {
defer closer()
}
require.NoError(t, err)
// initialize it future-way
opts, err := GenInitOptions(nil)
require.NoError(t, err)
req := abci.RequestInitChain{AppStateBytes: opts}
app.InitChain(req)
app.Commit()
// XXX test failing
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Data: []byte("foo"),
}
qres := app.Query(query)
require.Equal(t, uint32(0), qres.Code, qres.Log)
assert.Equal(t, []byte("bar"), qres.Value)
}
// TextDeliverTx ensures we can write a tx
func TestDeliverTx(t *testing.T) {
// set up an app
app, closer, err := SetupApp()
// closer may need to be run, even when error in later stage
if closer != nil {
defer closer()
}
require.NoError(t, err)
key := "my-special-key"
value := "top-secret-data!!"
tx := NewTx(key, value)
txBytes := tx.GetSignBytes()
header := abci.Header{
AppHash: []byte("apphash"),
Height: 1,
}
app.BeginBlock(abci.RequestBeginBlock{Header: header})
dres := app.DeliverTx(txBytes)
require.Equal(t, uint32(0), dres.Code, dres.Log)
app.EndBlock(abci.RequestEndBlock{})
cres := app.Commit()
require.NotEmpty(t, cres.Data)
// make sure we can query these values
query := abci.RequestQuery{
Path: "/main/key",
Data: []byte(key),
}
qres := app.Query(query)
require.Equal(t, uint32(0), qres.Code, qres.Log)
assert.Equal(t, []byte(value), qres.Value)
}

27
mock/helpers.go Normal file
View File

@ -0,0 +1,27 @@
package mock
import (
"io/ioutil"
"os"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
)
// SetupApp returns an application as well as a clean-up function
// to be used to quickly setup a test case with an app
func SetupApp() (abci.Application, func(), error) {
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock")
rootDir, err := ioutil.TempDir("", "mock-sdk")
if err != nil {
return nil, nil, err
}
cleanup := func() {
os.RemoveAll(rootDir)
}
app, err := NewApp(rootDir, logger)
return app, cleanup, err
}

89
mock/tx.go Normal file
View File

@ -0,0 +1,89 @@
//nolint
package mock
import (
"bytes"
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
crypto "github.com/tendermint/go-crypto"
)
// An sdk.Tx which is its own sdk.Msg.
type kvstoreTx struct {
key []byte
value []byte
bytes []byte
}
var _ sdk.Tx = kvstoreTx{}
func NewTx(key, value string) kvstoreTx {
bytes := fmt.Sprintf("%s=%s", key, value)
return kvstoreTx{
key: []byte(key),
value: []byte(value),
bytes: []byte(bytes),
}
}
func (tx kvstoreTx) Get(key interface{}) (value interface{}) {
switch k := key.(type) {
case string:
switch k {
case "key":
return tx.key
case "value":
return tx.value
}
}
return nil
}
func (tx kvstoreTx) Type() string {
return "kvstore"
}
func (tx kvstoreTx) GetMsg() sdk.Msg {
return tx
}
func (tx kvstoreTx) GetSignBytes() []byte {
return tx.bytes
}
// Should the app be calling this? Or only handlers?
func (tx kvstoreTx) ValidateBasic() sdk.Error {
return nil
}
func (tx kvstoreTx) GetSigners() []crypto.Address {
return nil
}
func (tx kvstoreTx) GetSignatures() []sdk.StdSignature {
return nil
}
func (tx kvstoreTx) GetFeePayer() crypto.Address {
return nil
}
// takes raw transaction bytes and decodes them into an sdk.Tx. An sdk.Tx has
// all the signatures and can be used to authenticate.
func decodeTx(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx sdk.Tx
split := bytes.Split(txBytes, []byte("="))
if len(split) == 1 {
k := split[0]
tx = kvstoreTx{k, k, txBytes}
} else if len(split) == 2 {
k, v := split[0], split[1]
tx = kvstoreTx{k, v, txBytes}
} else {
return nil, sdk.ErrTxParse("too many =")
}
return tx, nil
}

182
server/init.go Normal file
View File

@ -0,0 +1,182 @@
package server
import (
"encoding/json"
"fmt"
"io/ioutil"
"github.com/spf13/cobra"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-crypto/keys"
"github.com/tendermint/go-crypto/keys/words"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
"github.com/tendermint/tmlibs/log"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
cfg "github.com/tendermint/tendermint/config"
tmtypes "github.com/tendermint/tendermint/types"
)
// InitCmd will initialize all files for tendermint,
// along with proper app_options.
// The application can pass in a function to generate
// proper options. And may want to use GenerateCoinKey
// to create default account(s).
func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command {
cmd := initCmd{
gen: gen,
logger: logger,
}
return &cobra.Command{
Use: "init",
Short: "Initialize genesis files",
RunE: cmd.run,
}
}
// GenOptions can parse command-line and flag to
// generate default app_options for the genesis file.
// This is application-specific
type GenOptions func(args []string) (json.RawMessage, error)
// GenerateCoinKey returns the address of a public key,
// along with the secret phrase to recover the private key.
// You can give coins to this address and return the recovery
// phrase to the user to access them.
func GenerateCoinKey() (crypto.Address, string, error) {
// construct an in-memory key store
codec, err := words.LoadCodec("english")
if err != nil {
return nil, "", err
}
keybase := keys.New(
dbm.NewMemDB(),
codec,
)
// generate a private key, with recovery phrase
info, secret, err := keybase.Create("name", "pass", keys.AlgoEd25519)
if err != nil {
return nil, "", err
}
addr := info.PubKey.Address()
return addr, secret, nil
}
type initCmd struct {
gen GenOptions
logger log.Logger
}
func (c initCmd) run(cmd *cobra.Command, args []string) error {
// Run the basic tendermint initialization,
// set up a default genesis with no app_options
config, err := tcmd.ParseConfig()
if err != nil {
return err
}
err = c.initTendermintFiles(config)
if err != nil {
return err
}
// no app_options, leave like tendermint
if c.gen == nil {
return nil
}
// Now, we want to add the custom app_options
options, err := c.gen(args)
if err != nil {
return err
}
// And add them to the genesis file
genFile := config.GenesisFile()
return addGenesisOptions(genFile, options)
}
// This was copied from tendermint/cmd/tendermint/commands/init.go
// so we could pass in the config and the logger.
func (c initCmd) initTendermintFiles(config *cfg.Config) error {
// private validator
privValFile := config.PrivValidatorFile()
var privValidator *tmtypes.PrivValidatorFS
if cmn.FileExists(privValFile) {
privValidator = tmtypes.LoadPrivValidatorFS(privValFile)
c.logger.Info("Found private validator", "path", privValFile)
} else {
privValidator = tmtypes.GenPrivValidatorFS(privValFile)
privValidator.Save()
c.logger.Info("Generated private validator", "path", privValFile)
}
// genesis file
genFile := config.GenesisFile()
if cmn.FileExists(genFile) {
c.logger.Info("Found genesis file", "path", genFile)
} else {
genDoc := tmtypes.GenesisDoc{
ChainID: cmn.Fmt("test-chain-%v", cmn.RandStr(6)),
}
genDoc.Validators = []tmtypes.GenesisValidator{{
PubKey: privValidator.GetPubKey(),
Power: 10,
}}
if err := genDoc.SaveAs(genFile); err != nil {
return err
}
c.logger.Info("Generated genesis file", "path", genFile)
}
return nil
}
// GenesisDoc involves some tendermint-specific structures we don't
// want to parse, so we just grab it into a raw object format,
// so we can add one line.
type GenesisDoc map[string]json.RawMessage
func addGenesisOptions(filename string, options json.RawMessage) error {
bz, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
var doc GenesisDoc
err = json.Unmarshal(bz, &doc)
if err != nil {
return err
}
doc["app_state"] = options
out, err := json.MarshalIndent(doc, "", " ")
if err != nil {
return err
}
return ioutil.WriteFile(filename, out, 0600)
}
// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options
// that grant a large amount of "mycoin" to a single address
// TODO: A better UX for generating genesis files
func GetGenesisJSON(pubkey, chainID, denom, addr string, options string) string {
return fmt.Sprintf(`{
"accounts": [{
"address": "%s",
"coins": [
{
"denom": "%s",
"amount": 9007199254740992
}
]
}],
"plugin_options": [
"coin/issuer", {"app": "sigs", "addr": "%s"}%s
]
}`, addr, denom, addr, options)
}

36
server/init_test.go Normal file
View File

@ -0,0 +1,36 @@
package server
import (
"io/ioutil"
"os"
"testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/tendermint/tmlibs/log"
"github.com/cosmos/cosmos-sdk/mock"
)
// setupViper creates a homedir to run inside,
// and returns a cleanup function to defer
func setupViper() func() {
rootDir, err := ioutil.TempDir("", "mock-sdk-cmd")
if err != nil {
panic(err) // fuck it!
}
viper.Set("home", rootDir)
return func() {
os.RemoveAll(rootDir)
}
}
func TestInit(t *testing.T) {
defer setupViper()()
logger := log.NewNopLogger()
cmd := InitCmd(mock.GenInitOptions, logger)
err := cmd.RunE(nil, nil)
require.NoError(t, err)
}

31
server/reset.go Normal file
View File

@ -0,0 +1,31 @@
package server
import (
"github.com/spf13/cobra"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tmlibs/log"
)
// UnsafeResetAllCmd - extension of the tendermint command, resets initialization
func UnsafeResetAllCmd(logger log.Logger) *cobra.Command {
cmd := resetAll{logger}
return &cobra.Command{
Use: "unsafe_reset_all",
Short: "Reset all blockchain data",
RunE: cmd.run,
}
}
type resetAll struct {
logger log.Logger
}
func (r resetAll) run(cmd *cobra.Command, args []string) error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
tcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), r.logger)
return nil
}

119
server/start.go Normal file
View File

@ -0,0 +1,119 @@
package server
import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/abci/server"
abci "github.com/tendermint/abci/types"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
const (
flagWithTendermint = "with-tendermint"
flagAddress = "address"
)
// appGenerator lets us lazily initialize app, using home dir
// and other flags (?) to start
type appGenerator func(string, log.Logger) (abci.Application, error)
// StartCmd runs the service passed in, either
// stand-alone, or in-process with tendermint
func StartCmd(app appGenerator, logger log.Logger) *cobra.Command {
start := startCmd{
app: app,
logger: logger,
}
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
RunE: start.run,
}
// basic flags for abci app
cmd.Flags().Bool(flagWithTendermint, true, "run abci app embedded in-process with tendermint")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:46658", "Listen address")
// AddNodeFlags adds support for all
// tendermint-specific command line options
tcmd.AddNodeFlags(cmd)
return cmd
}
type startCmd struct {
app appGenerator
logger log.Logger
}
func (s startCmd) run(cmd *cobra.Command, args []string) error {
if !viper.GetBool(flagWithTendermint) {
s.logger.Info("Starting ABCI without Tendermint")
return s.startStandAlone()
}
s.logger.Info("Starting ABCI with Tendermint")
return s.startInProcess()
}
func (s startCmd) startStandAlone() error {
// Generate the app in the proper dir
addr := viper.GetString(flagAddress)
home := viper.GetString("home")
app, err := s.app(home, s.logger)
if err != nil {
return err
}
svr, err := server.NewServer(addr, "socket", app)
if err != nil {
return errors.Errorf("Error creating listener: %v\n", err)
}
svr.SetLogger(s.logger.With("module", "abci-server"))
svr.Start()
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
svr.Stop()
})
return nil
}
func (s startCmd) startInProcess() error {
cfg, err := tcmd.ParseConfig()
if err != nil {
return err
}
home := cfg.RootDir
app, err := s.app(home, s.logger)
if err != nil {
return err
}
// Create & start tendermint node
n, err := node.NewNode(cfg,
types.LoadOrGenPrivValidatorFS(cfg.PrivValidatorFile()),
proxy.NewLocalClientCreator(app),
node.DefaultGenesisDocProviderFunc(cfg),
node.DefaultDBProvider,
s.logger.With("module", "node"))
if err != nil {
return err
}
err = n.Start()
if err != nil {
return err
}
// Trap signal, run forever.
n.RunForever()
return nil
}

72
server/start_test.go Normal file
View File

@ -0,0 +1,72 @@
package server
import (
"fmt"
"os"
"testing"
"time"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/mock"
"github.com/tendermint/tmlibs/log"
)
func TestStartStandAlone(t *testing.T) {
defer setupViper()()
logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
viper.Set(flagWithTendermint, false)
viper.Set(flagAddress, "localhost:11122")
startCmd := StartCmd(mock.NewApp, logger)
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
}
func TestStartWithTendermint(t *testing.T) {
defer setupViper()()
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).
With("module", "mock-cmd")
// logger := log.NewNopLogger()
initCmd := InitCmd(mock.GenInitOptions, logger)
err := initCmd.RunE(nil, nil)
require.NoError(t, err)
// set up app and start up
viper.Set(flagWithTendermint, true)
startCmd := StartCmd(mock.NewApp, logger)
timeout := time.Duration(3) * time.Second
err = runOrTimeout(startCmd, timeout)
require.NoError(t, err)
}
func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error {
done := make(chan error)
go func(out chan<- error) {
// this should NOT exit
err := cmd.RunE(nil, nil)
if err != nil {
out <- err
}
out <- fmt.Errorf("start died for unknown reasons")
}(done)
timer := time.NewTimer(timeout)
select {
case err := <-done:
return err
case <-timer.C:
return nil
}
}

View File

@ -67,6 +67,11 @@ func (rs *rootMultiStore) GetCommitStore(key StoreKey) CommitStore {
return rs.stores[key]
}
// Implements CommitMultiStore.
func (rs *rootMultiStore) GetCommitKVStore(key StoreKey) CommitKVStore {
return rs.stores[key].(CommitKVStore)
}
// Implements CommitMultiStore.
func (rs *rootMultiStore) LoadLatestVersion() error {
ver := getLatestVersion(rs.db)

View File

@ -6,14 +6,15 @@ import (
// Import cosmos-sdk/types/store.go for convenience.
type Store = types.Store
type Committer = types.Committer
type CommitStore = types.CommitStore
type MultiStore = types.MultiStore
type CacheMultiStore = types.CacheMultiStore
type CommitStore = types.CommitStore
type Committer = types.Committer
type CommitMultiStore = types.CommitMultiStore
type KVStore = types.KVStore
type Iterator = types.Iterator
type CacheKVStore = types.CacheKVStore
type CommitKVStore = types.CommitKVStore
type CacheWrapper = types.CacheWrapper
type CacheWrap = types.CacheWrap
type CommitID = types.CommitID

69
tests/check_basecli.sh Executable file
View File

@ -0,0 +1,69 @@
#!/bin/sh
# Note: Bucky, I know you want to kill bash tests.
# Please show me how to do an alternative to this.
# I would rather get code running before I leave than
# fight trying to invent some new test harness that
# no one else will understand.
#
# Thus, I leave this as an exercise to the reader to
# port into a non-bash version. And I don't do it proper...
# just automate my manual tests
# WARNING!!!
rm -rf ~/.basecoind ~/.basecli
cd $GOPATH/src/github.com/cosmos/cosmos-sdk
# make get_vendor_deps
make build
# init stuff
SEED=`./build/basecoind init | tail -1`
PASS='some-silly-123'
(echo $PASS; echo $SEED) | ./build/basecli keys add demo --recover
ADDR=`./build/basecli keys show demo | cut -f3`
echo "Recovered seed for demo:" $ADDR
# start up server
./build/basecoind start > ~/.basecoind/basecoind.log 2>&1 &
sleep 5
PID_SERVER=$!
# query original state
TO='ABCAFE00DEADBEEF00CAFE00DEADBEEF00CAFE00'
echo; echo "My account:" $ADDR
./build/basecli account $ADDR
echo; echo "Empty account:" $TO
./build/basecli account $TO
# send some money
TX=`echo $PASS | ./build/basecli send --to=$TO --amount=1000mycoin --name=demo --seq=0`
echo; echo "SendTx"; echo $TX
HASH=`echo $TX | cut -d' ' -f6`
echo "tx hash:" $HASH
# let some blocks come up....
./build/basecli status | jq .latest_block_height
sleep 2
./build/basecli status | jq .latest_block_height
# balances change
echo; echo "My account went down"
./build/basecli account $ADDR
echo; echo "Empty account got some cash"
./build/basecli account $TO
# query original tx
echo; echo "View tx"
./build/basecli tx $HASH
# wait a bit then dump out some blockchain state
sleep 10
./build/basecli status --trace
./build/basecli block --trace
./build/basecli validatorset --trace
# shutdown, but add a sleep if you want to manually run some cli scripts
# against this server before it goes away
# sleep 120
kill $PID_SERVER

View File

@ -40,10 +40,10 @@ install: get_vendor_deps
@echo "$(ansi_grn)Installing tools$(ansi_end)"
@echo "$(ansi_yel)Install go-vendorinstall$(ansi_end)"
go build -o bin/go-vendorinstall go-vendorinstall/*.go
@echo "$(ansi_yel)Install gometalinter.v2$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall gopkg.in/alecthomas/gometalinter.v2
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/alecthomas/gometalinter
@echo "$(ansi_yel)Install shelldown$(ansi_end)"
GOBIN=$(CURDIR)/bin ./bin/go-vendorinstall github.com/rigelrozanski/shelldown/cmd/shelldown

14
tools/glide.lock generated
View File

@ -1,8 +1,8 @@
hash: a163b1c4806024cfc9062db75a0abed285ec40461243e59af0e147db2c4bf0ce
updated: 2018-01-15T19:02:49.834182027-08:00
hash: 934ad5be72c9c240e8555eb6e1b2319840266c04c0fa9e024008cf841c0cee65
updated: 2018-02-23T19:33:08.596187+01:00
imports:
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/alecthomas/gometalinter
version: 46cc1ea3778b247666c2949669a3333c532fa9c6
- name: github.com/rigelrozanski/common
version: f691f115798593d783b9999b1263c2f4ffecc439
- name: github.com/rigelrozanski/shelldown
@ -12,7 +12,7 @@ imports:
- name: github.com/spf13/cobra
version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b
- name: github.com/spf13/pflag
version: 97afa5e7ca8a08a383cb259e06636b5e2cc7897f
- name: gopkg.in/alecthomas/gometalinter.v2
version: 88d47c66988c5a5cb3945925da47c883800a94df
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 25b30aa063fc18e48662b86996252eabdcf2f0c7
testImports: []

View File

@ -1,6 +1,13 @@
package: github.com/cosmos/cosmos-sdk/tools
import:
- package: github.com/alecthomas/gometalinter
version: ^2.0.5
- package: github.com/rigelrozanski/shelldown
subpackages:
- cmd/shelldown
- package: gopkg.in/alecthomas/gometalinter.v2
- package: github.com/spf13/pflag
version: v1.0.0
- package: github.com/spf13/cobra
version: v0.0.1
- package: github.com/spf13/viper
version: ^1.0.0

View File

@ -30,3 +30,6 @@ type AccountMapper interface {
GetAccount(ctx Context, addr crypto.Address) Account
SetAccount(ctx Context, acc Account)
}
// Application function variable used to unmarshal account
type ParseAccount func([]byte) (Account, error)

View File

@ -4,7 +4,7 @@ import crypto "github.com/tendermint/go-crypto"
// Standard Signature
type StdSignature struct {
crypto.PubKey // optional
crypto.Signature
Sequence int64
crypto.PubKey `json:"pub_key"` // optional
crypto.Signature `json:"signature"`
Sequence int64 `json:"sequence"`
}

View File

@ -68,6 +68,9 @@ type CommitMultiStore interface {
// Panics on a nil key.
GetCommitStore(key StoreKey) CommitStore
// Panics on a nil key.
GetCommitKVStore(key StoreKey) CommitKVStore
// Load the latest persisted version. Called once after all
// calls to Mount*Store() are complete.
LoadLatestVersion() error
@ -129,6 +132,12 @@ type CacheKVStore interface {
Write()
}
// Stores of MultiStore must implement CommitStore.
type CommitKVStore interface {
Committer
KVStore
}
//----------------------------------------
// CacheWrap

View File

@ -52,8 +52,8 @@ var _ Tx = (*StdTx)(nil)
// StdTx is a standard way to wrap a Msg with Signatures.
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
type StdTx struct {
Msg
Signatures []StdSignature
Msg `json:"msg"`
Signatures []StdSignature `json:"signatures"`
}
func NewStdTx(msg Msg, sigs []StdSignature) StdTx {

View File

@ -1,11 +1,9 @@
package main
package version
import (
"fmt"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/version"
)
var (
@ -18,9 +16,9 @@ var (
)
func doVersionCmd(cmd *cobra.Command, args []string) {
v := version.Version
if version.GitCommit != "" {
v = v + " " + version.GitCommit
v := Version
if GitCommit != "" {
v = v + " " + GitCommit
}
fmt.Println(v)
}

View File

@ -6,10 +6,10 @@ package version
// TODO improve
const Maj = "0"
const Min = "10"
const Min = "11"
const Fix = "0"
const Version = "0.10.0"
const Version = "0.11.0"
// GitCommit set by build flags
var GitCommit = ""

View File

@ -0,0 +1,82 @@
package commands
import (
"encoding/hex"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
)
// GetAccountCmd for the auth.BaseAccount type
func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command {
return GetAccountCmd(storeName, cdc, getParseAccount(cdc))
}
func getParseAccount(cdc *wire.Codec) sdk.ParseAccount {
return func(accBytes []byte) (sdk.Account, error) {
acct := new(auth.BaseAccount)
err := cdc.UnmarshalBinary(accBytes, acct)
return acct, err
}
}
// GetAccountCmd returns a query account that will display the
// state of the account at a given address
func GetAccountCmd(storeName string, cdc *wire.Codec, parser sdk.ParseAccount) *cobra.Command {
cmdr := commander{
storeName,
cdc,
parser,
}
return &cobra.Command{
Use: "account <address>",
Short: "Query account balance",
RunE: cmdr.getAccountCmd,
}
}
type commander struct {
storeName string
cdc *wire.Codec
parser sdk.ParseAccount
}
func (c commander) getAccountCmd(cmd *cobra.Command, args []string) error {
if len(args) != 1 || len(args[0]) == 0 {
return errors.New("You must provide an account name")
}
// find the key to look up the account
addr := args[0]
bz, err := hex.DecodeString(addr)
if err != nil {
return err
}
key := crypto.Address(bz)
res, err := client.Query(key, c.storeName)
// parse out the value
account, err := c.parser(res)
if err != nil {
return err
}
// print out whole account
output, err := json.MarshalIndent(account, "", " ")
if err != nil {
return err
}
fmt.Println(string(output))
return nil
}

129
x/bank/commands/sendtx.go Normal file
View File

@ -0,0 +1,129 @@
package commands
import (
"encoding/hex"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
"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/bank"
)
const (
flagTo = "to"
flagAmount = "amount"
flagFee = "fee"
flagSequence = "seq"
)
// SendTxCommand will create a send tx and sign it with the given key
func SendTxCmd(cdc *wire.Codec) *cobra.Command {
cmdr := commander{cdc}
cmd := &cobra.Command{
Use: "send",
Short: "Create and sign a send tx",
RunE: cmdr.sendTxCmd,
}
cmd.Flags().String(flagTo, "", "Address to send coins")
cmd.Flags().String(flagAmount, "", "Amount of coins to send")
cmd.Flags().String(flagFee, "", "Fee to pay along with transaction")
cmd.Flags().Int64(flagSequence, 0, "Sequence number to sign the tx")
return cmd
}
type commander struct {
cdc *wire.Codec
}
func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error {
txBytes, err := c.buildTx()
if err != nil {
return err
}
res, err := client.BroadcastTx(txBytes)
if err != nil {
return err
}
fmt.Printf("Committed at block %d. Hash: %s\n", res.Height, res.Hash.String())
return nil
}
func (c commander) buildTx() ([]byte, error) {
keybase, err := keys.GetKeyBase()
if err != nil {
return nil, err
}
name := viper.GetString(client.FlagName)
info, err := keybase.Get(name)
if err != nil {
return nil, errors.Errorf("No key for: %s", name)
}
from := info.PubKey.Address()
msg, err := buildMsg(from)
if err != nil {
return nil, err
}
// sign and build
bz := msg.GetSignBytes()
buf := client.BufferStdin()
prompt := fmt.Sprintf("Password to sign with '%s':", name)
passphrase, err := client.GetPassword(prompt, buf)
if err != nil {
return nil, err
}
sig, pubkey, err := keybase.Sign(name, passphrase, bz)
if err != nil {
return nil, err
}
sigs := []sdk.StdSignature{{
PubKey: pubkey,
Signature: sig,
Sequence: viper.GetInt64(flagSequence),
}}
// marshal bytes
tx := sdk.NewStdTx(msg, sigs)
txBytes, err := c.cdc.MarshalBinary(tx)
if err != nil {
return nil, err
}
return txBytes, nil
}
func buildMsg(from crypto.Address) (sdk.Msg, error) {
// parse coins
amount := viper.GetString(flagAmount)
coins, err := sdk.ParseCoins(amount)
if err != nil {
return nil, err
}
// parse destination address
dest := viper.GetString(flagTo)
bz, err := hex.DecodeString(dest)
if err != nil {
return nil, err
}
to := crypto.Address(bz)
input := bank.NewInput(from, coins)
output := bank.NewOutput(to, coins)
msg := bank.NewSendMsg([]bank.Input{input}, []bank.Output{output})
return msg, nil
}

View File

@ -39,6 +39,7 @@ func handleSendMsg(ctx sdk.Context, ck CoinKeeper, msg SendMsg) sdk.Result {
}
}
// TODO: add some tags so we can search it!
return sdk.Result{} // TODO
}

View File

@ -1,6 +1,7 @@
package bank
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@ -10,6 +11,30 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestNewSendMsg(t *testing.T) {}
func TestSendMsgType(t *testing.T) {
// Construct a SendMsg
var msg = SendMsg{
Inputs: []Input{
{
Address: crypto.Address([]byte("input")),
Coins: sdk.Coins{{"atom", 10}},
Sequence: 1,
},
},
Outputs: []Output{
{
Address: crypto.Address([]byte("output")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
// TODO some failures for bad result
assert.Equal(t, msg.Type(), "bank")
}
func TestInputValidation(t *testing.T) {
addr1 := crypto.Address([]byte{1, 2})
addr2 := crypto.Address([]byte{7, 8})
@ -97,7 +122,6 @@ func TestOutputValidation(t *testing.T) {
}
func TestSendMsgValidation(t *testing.T) {
addr1 := crypto.Address([]byte{1, 2})
addr2 := crypto.Address([]byte{7, 8})
atom123 := sdk.Coins{{"atom", 123}}
@ -165,8 +189,89 @@ func TestSendMsgValidation(t *testing.T) {
}
}
func TestSendMsgString(t *testing.T) {
// Construct a SendMsg
var msg = SendMsg{
Inputs: []Input{
{
Address: crypto.Address([]byte("input")),
Coins: sdk.Coins{{"atom", 10}},
Sequence: 1,
},
},
Outputs: []Output{
{
Address: crypto.Address([]byte("output")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.String()
// TODO some failures for bad results
assert.Equal(t, res, "SendMsg{[Input{696E707574,10atom}]->[Output{364637353734373037353734,10atom}]}")
}
func TestSendMsgGet(t *testing.T) {
var msg = SendMsg{
Inputs: []Input{
{
Address: crypto.Address([]byte("input")),
Coins: sdk.Coins{{"atom", 10}},
Sequence: 1,
},
},
Outputs: []Output{
{
Address: crypto.Address([]byte("output")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.Get(nil)
assert.Nil(t, res)
}
func TestSendMsgGetSignBytes(t *testing.T) {
var msg = SendMsg{
Inputs: []Input{
{
Address: crypto.Address([]byte("input")),
Coins: sdk.Coins{{"atom", 10}},
Sequence: 1,
},
},
Outputs: []Output{
{
Address: crypto.Address([]byte("output")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.GetSignBytes()
// TODO bad results
assert.Equal(t, string(res), `{"inputs":[{"address":"696E707574","coins":[{"denom":"atom","amount":10}],"sequence":1}],"outputs":[{"address":"6F7574707574","coins":[{"denom":"atom","amount":10}]}]}`)
}
func TestSendMsgGetSigners(t *testing.T) {
var msg = SendMsg{
Inputs: []Input{
{
Address: crypto.Address([]byte("input1")),
},
{
Address: crypto.Address([]byte("input2")),
},
{
Address: crypto.Address([]byte("input3")),
},
},
}
res := msg.GetSigners()
assert.Equal(t, fmt.Sprintf("%v", res), "[696E70757431 696E70757432 696E70757433]")
}
/*
// TODO where does this test belong ?
// what to do w/ this test?
func TestSendMsgSigners(t *testing.T) {
signers := []crypto.Address{
{1, 2, 3},
@ -184,3 +289,82 @@ func TestSendMsgSigners(t *testing.T) {
assert.Equal(t, signers, tx.Signers())
}
*/
// ----------------------------------------
// IssueMsg Tests
func TestNewIssueMsg(t *testing.T) {
// TODO
}
func TestIssueMsgType(t *testing.T) {
// Construct an IssueMsg
var msg = IssueMsg{
Banker: crypto.Address([]byte("input")),
Outputs: []Output{
{
Address: crypto.Address([]byte("loan-from-bank")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
// TODO some failures for bad result
assert.Equal(t, msg.Type(), "bank")
}
func TestIssueMsgValidation(t *testing.T) {
// TODO
}
func TestIssueMsgString(t *testing.T) {
// Construct a IssueMsg
var msg = IssueMsg{
Banker: crypto.Address([]byte("input")),
Outputs: []Output{
{
Address: crypto.Address([]byte("loan-from-bank")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.String()
assert.Equal(t, res, "IssueMsg{696E707574#[Output{36433646363136453244363637323646364432443632363136453642,10atom}]}")
}
func TestIssueMsgGet(t *testing.T) {
var msg = IssueMsg{
Banker: crypto.Address([]byte("input")),
Outputs: []Output{
{
Address: crypto.Address([]byte("loan-from-bank")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.Get(nil)
assert.Nil(t, res)
}
func TestIssueMsgGetSignBytes(t *testing.T) {
var msg = IssueMsg{
Banker: crypto.Address([]byte("input")),
Outputs: []Output{
{
Address: crypto.Address([]byte("loan-from-bank")),
Coins: sdk.Coins{{"atom", 10}},
},
},
}
res := msg.GetSignBytes()
// TODO bad results
assert.Equal(t, string(res), `{"banker":"696E707574","outputs":[{"address":"6C6F616E2D66726F6D2D62616E6B","coins":[{"denom":"atom","amount":10}]}]}`)
}
func TestIssueMsgGetSigners(t *testing.T) {
var msg = IssueMsg{
Banker: crypto.Address([]byte("onlyone")),
}
res := msg.GetSigners()
assert.Equal(t, fmt.Sprintf("%v", res), "[6F6E6C796F6E65]")
}