From 651e931763acb5bcbb0020b7bc41cd2f1de4184a Mon Sep 17 00:00:00 2001 From: Yukai Tu Date: Tue, 13 Mar 2018 11:48:51 -0700 Subject: [PATCH 01/64] Fix Issue#618 --- baseapp/baseapp.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/baseapp/baseapp.go b/baseapp/baseapp.go index c6ca46e0f8..3dad0483a3 100644 --- a/baseapp/baseapp.go +++ b/baseapp/baseapp.go @@ -371,6 +371,13 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk } } + // Match route. + msgType := msg.Type() + handler := app.router.Route(msgType) + if handler == nil { + return sdk.ErrUnknownRequest("Unrecognized Msg type: " + msgType).Result() + } + // Get the correct cache var msCache sdk.CacheMultiStore if isCheckTx == true { @@ -384,9 +391,6 @@ func (app *BaseApp) runTx(isCheckTx bool, txBytes []byte, tx sdk.Tx) (result sdk } - // Match and run route. - msgType := msg.Type() - handler := app.router.Route(msgType) result = handler(ctx, msg) // If result was successful, write to app.checkState.ms or app.deliverState.ms From 2336a20f5a0619d456b0e72eb18a966d5ed2d93a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 14 Mar 2018 13:11:19 +0100 Subject: [PATCH 02/64] fix cli to sign StdSignDoc. fixes #620 --- client/builder/builder.go | 10 +++++----- examples/basecoin/x/cool/commands/tx.go | 23 +++++++++++++++++++++-- types/tx_msg.go | 13 +++++++++++++ x/bank/commands/sendtx.go | 12 +++++++++++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/client/builder/builder.go b/client/builder/builder.go index f36cc159de..31e5189492 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -88,7 +88,7 @@ func GetFromAddress() (from sdk.Address, err error) { } // sign and build the transaction from the msg -func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { +func SignAndBuild(signMsg sdk.StdSignMsg, cdc *wire.Codec) ([]byte, error) { keybase, err := keys.GetKeyBase() if err != nil { @@ -97,7 +97,7 @@ func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { name := viper.GetString(client.FlagName) // sign and build - bz := msg.GetSignBytes() + bz := signMsg.Bytes() buf := client.BufferStdin() prompt := fmt.Sprintf("Password to sign with '%s':", name) passphrase, err := client.GetPassword(prompt, buf) @@ -115,14 +115,14 @@ func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { }} // marshal bytes - tx := sdk.NewStdTx(msg, sigs) + tx := sdk.NewStdTx(signMsg.Msg, sigs) return cdc.MarshalBinary(tx) } // sign and build the transaction from the msg -func SignBuildBroadcast(msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { - txBytes, err := SignAndBuild(msg, cdc) +func SignBuildBroadcast(signMsg sdk.StdSignMsg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { + txBytes, err := SignAndBuild(signMsg, cdc) if err != nil { return nil, err } diff --git a/examples/basecoin/x/cool/commands/tx.go b/examples/basecoin/x/cool/commands/tx.go index 2b16546806..71e80c98eb 100644 --- a/examples/basecoin/x/cool/commands/tx.go +++ b/examples/basecoin/x/cool/commands/tx.go @@ -5,8 +5,11 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool" @@ -30,9 +33,17 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { // create the message msg := cool.NewQuizMsg(from, args[0]) + chainID := viper.GetString(client.FlagChainID) + sequence := int64(viper.GetInt(client.FlagSequence)) + + signMsg := sdk.StdSignMsg{ + ChainID: chainID, + Sequences: []int64{sequence}, + Msg: msg, + } // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(signMsg, cdc) if err != nil { return err } @@ -61,9 +72,17 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { // create the message msg := cool.NewSetTrendMsg(from, args[0]) + chainID := viper.GetString(client.FlagChainID) + sequence := int64(viper.GetInt(client.FlagSequence)) + + signMsg := sdk.StdSignMsg{ + ChainID: chainID, + Sequences: []int64{sequence}, + Msg: msg, + } // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(signMsg, cdc) if err != nil { return err } diff --git a/types/tx_msg.go b/types/tx_msg.go index 63f9347265..141f9d050d 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -80,6 +80,19 @@ type StdSignDoc struct { AltBytes []byte `json:"alt_bytes"` // TODO: do we really want this ? } +// StdSignMsg is a convenience structure for passing along +// a Msg with the other requirements for a StdSignDoc before +// it is signed. For use in the CLI +type StdSignMsg struct { + ChainID string + Sequences []int64 + Msg Msg +} + +func (msg StdSignMsg) Bytes() []byte { + return StdSignBytes(msg.ChainID, msg.Sequences, msg.Msg) +} + func StdSignBytes(chainID string, sequences []int64, msg Msg) []byte { bz, err := json.Marshal(StdSignDoc{ ChainID: chainID, diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 7e6dd463dd..6ce0097a8e 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -50,8 +51,17 @@ func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { return err } + chainID := viper.GetString(client.FlagChainID) + sequence := int64(viper.GetInt(client.FlagSequence)) + + signMsg := sdk.StdSignMsg{ + ChainID: chainID, + Sequences: []int64{sequence}, + Msg: msg, + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, c.cdc) + res, err := builder.SignBuildBroadcast(signMsg, c.cdc) if err != nil { return err } From 861eb5da1c9eb74fb34d0af16862906515eb64e1 Mon Sep 17 00:00:00 2001 From: mossid Date: Wed, 14 Mar 2018 19:20:15 +0100 Subject: [PATCH 03/64] revert & rebase; add stdlib and test --- tests/stdlib_test.go | 69 +++++++++++++ types/stdlib.go | 231 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 tests/stdlib_test.go create mode 100644 types/stdlib.go diff --git a/tests/stdlib_test.go b/tests/stdlib_test.go new file mode 100644 index 0000000000..c2d47f6b52 --- /dev/null +++ b/tests/stdlib_test.go @@ -0,0 +1,69 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + dbm "github.com/tendermint/tmlibs/db" + + abci "github.com/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type S struct { + I int64 + B bool +} + +func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { + db := dbm.NewMemDB() + cms := store.NewCommitMultiStore(db) + cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, db) + cms.LoadLatestVersion() + ctx := sdk.NewContext(cms, abci.Header{}, false, nil) + cdc := wire.NewCodec() + return ctx, cdc +} + +func TestListMapper(t *testing.T) { + key := sdk.NewKVStoreKey("list") + ctx, cdc := defaultComponents(key) + lm := sdk.NewListMapper(cdc, key) + + val := S{1, true} + var res S + + lm.Push(ctx, val) + assert.Equal(t, int64(1), lm.Len(ctx)) + lm.Get(ctx, int64(0), &res) + assert.Equal(t, val, res) + + val = S{2, false} + lm.Set(ctx, int64(0), val) + lm.Get(ctx, int64(0), &res) + assert.Equal(t, val, res) +} + +func TestQueueMapper(t *testing.T) { + key := sdk.NewKVStoreKey("queue") + ctx, cdc := defaultComponents(key) + qm := sdk.NewQueueMapper(cdc, key) + + val := S{1, true} + var res S + + qm.Push(ctx, val) + qm.Peek(ctx, &res) + assert.Equal(t, val, res) + + qm.Pop(ctx) + empty := qm.IsEmpty(ctx) + + assert.Equal(t, true, empty) + + assert.Panics(t, func() { qm.Peek(ctx, &res) }) +} diff --git a/types/stdlib.go b/types/stdlib.go new file mode 100644 index 0000000000..d6c6c3f424 --- /dev/null +++ b/types/stdlib.go @@ -0,0 +1,231 @@ +package types + +import ( + "errors" + + wire "github.com/cosmos/cosmos-sdk/wire" +) + +type ListMapper interface { // Solidity list like structure + Len(Context) int64 + Get(Context, int64, interface{}) + Set(Context, int64, interface{}) + Push(Context, interface{}) + Iterate(Context, interface{}, func(Context, int64)) +} + +type listMapper struct { + key StoreKey + cdc *wire.Codec + lk []byte +} + +func NewListMapper(cdc *wire.Codec, key StoreKey) ListMapper { + lk, err := cdc.MarshalBinary(int64(-1)) + if err != nil { + panic(err) + } + return listMapper{ + key: key, + cdc: cdc, + lk: lk, + } +} + +func (lm listMapper) Len(ctx Context) int64 { + store := ctx.KVStore(lm.key) + bz := store.Get(lm.lk) + if bz == nil { + zero, err := lm.cdc.MarshalBinary(0) + if err != nil { + panic(err) + } + store.Set(lm.lk, zero) + return 0 + } + var res int64 + if err := lm.cdc.UnmarshalBinary(bz, &res); err != nil { + panic(err) + } + return res +} + +func (lm listMapper) Get(ctx Context, index int64, ptr interface{}) { + if index < 0 { + panic(errors.New("")) + } + store := ctx.KVStore(lm.key) + bz := store.Get(marshalInt64(lm.cdc, index)) + if err := lm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } +} + +func (lm listMapper) Set(ctx Context, index int64, value interface{}) { + if index < 0 { + panic(errors.New("")) + } + store := ctx.KVStore(lm.key) + bz, err := lm.cdc.MarshalBinary(value) + if err != nil { + panic(err) + } + store.Set(marshalInt64(lm.cdc, index), bz) +} + +func (lm listMapper) Push(ctx Context, value interface{}) { + length := lm.Len(ctx) + lm.Set(ctx, length, value) + + store := ctx.KVStore(lm.key) + store.Set(lm.lk, marshalInt64(lm.cdc, length+1)) +} + +func (lm listMapper) Iterate(ctx Context, ptr interface{}, fn func(Context, int64)) { + length := lm.Len(ctx) + for i := int64(0); i < length; i++ { + lm.Get(ctx, i, ptr) + fn(ctx, i) + } +} + +type QueueMapper interface { + Push(Context, interface{}) + Peek(Context, interface{}) + Pop(Context) + IsEmpty(Context) bool + Iterate(Context, interface{}, func(Context) bool) +} + +type queueMapper struct { + key StoreKey + cdc *wire.Codec + ik []byte +} + +func NewQueueMapper(cdc *wire.Codec, key StoreKey) QueueMapper { + ik, err := cdc.MarshalBinary(int64(-1)) + if err != nil { + panic(err) + } + return queueMapper{ + key: key, + cdc: cdc, + ik: ik, + } +} + +type queueInfo struct { + // begin <= elems < end + Begin int64 + End int64 +} + +func (info queueInfo) validateBasic() error { + if info.End < info.Begin || info.Begin < 0 || info.End < 0 { + return errors.New("") + } + return nil +} + +func (info queueInfo) isEmpty() bool { + return info.Begin == info.End +} + +func (qm queueMapper) getQueueInfo(store KVStore) queueInfo { + bz := store.Get(qm.ik) + if bz == nil { + store.Set(qm.ik, marshalQueueInfo(qm.cdc, queueInfo{0, 0})) + return queueInfo{0, 0} + } + var info queueInfo + if err := qm.cdc.UnmarshalBinary(bz, &info); err != nil { + panic(err) + } + if err := info.validateBasic(); err != nil { + panic(err) + } + return info +} + +func (qm queueMapper) setQueueInfo(store KVStore, info queueInfo) { + bz, err := qm.cdc.MarshalBinary(info) + if err != nil { + panic(err) + } + store.Set(qm.ik, bz) +} + +func (qm queueMapper) Push(ctx Context, value interface{}) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + + bz, err := qm.cdc.MarshalBinary(value) + if err != nil { + panic(err) + } + store.Set(marshalInt64(qm.cdc, info.End), bz) + + info.End++ + qm.setQueueInfo(store, info) +} + +func (qm queueMapper) Peek(ctx Context, ptr interface{}) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + bz := store.Get(marshalInt64(qm.cdc, info.Begin)) + if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } +} + +func (qm queueMapper) Pop(ctx Context) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + store.Delete(marshalInt64(qm.cdc, info.Begin)) + info.Begin++ + qm.setQueueInfo(store, info) +} + +func (qm queueMapper) IsEmpty(ctx Context) bool { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + return info.isEmpty() +} + +func (qm queueMapper) Iterate(ctx Context, ptr interface{}, fn func(Context) bool) { + store := ctx.KVStore(qm.key) + info := qm.getQueueInfo(store) + + var i int64 + for i = info.Begin; i < info.End; i++ { + key := marshalInt64(qm.cdc, i) + bz := store.Get(key) + if err := qm.cdc.UnmarshalBinary(bz, ptr); err != nil { + panic(err) + } + store.Delete(key) + if fn(ctx) { + break + } + } + + info.Begin = i + qm.setQueueInfo(store, info) +} + +func marshalQueueInfo(cdc *wire.Codec, info queueInfo) []byte { + bz, err := cdc.MarshalBinary(info) + if err != nil { + panic(err) + } + return bz +} + +func marshalInt64(cdc *wire.Codec, i int64) []byte { + bz, err := cdc.MarshalBinary(i) + if err != nil { + panic(err) + } + return bz +} From 806b4b2603d610676322b2761022552a226e52be Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 15 Mar 2018 10:59:36 +0100 Subject: [PATCH 04/64] types/stdlib --- types/{ => stdlib}/stdlib.go | 53 +++++++++++++------------- {tests => types/stdlib}/stdlib_test.go | 4 +- 2 files changed, 29 insertions(+), 28 deletions(-) rename types/{ => stdlib}/stdlib.go (71%) rename {tests => types/stdlib}/stdlib_test.go (94%) diff --git a/types/stdlib.go b/types/stdlib/stdlib.go similarity index 71% rename from types/stdlib.go rename to types/stdlib/stdlib.go index d6c6c3f424..dd9f4efad1 100644 --- a/types/stdlib.go +++ b/types/stdlib/stdlib.go @@ -3,24 +3,25 @@ package types import ( "errors" + sdk "github.com/cosmos/cosmos-sdk/types" wire "github.com/cosmos/cosmos-sdk/wire" ) type ListMapper interface { // Solidity list like structure - Len(Context) int64 - Get(Context, int64, interface{}) - Set(Context, int64, interface{}) - Push(Context, interface{}) - Iterate(Context, interface{}, func(Context, int64)) + Len(sdk.Context) int64 + Get(sdk.Context, int64, interface{}) + Set(sdk.Context, int64, interface{}) + Push(sdk.Context, interface{}) + Iterate(sdk.Context, interface{}, func(sdk.Context, int64)) } type listMapper struct { - key StoreKey + key sdk.StoreKey cdc *wire.Codec lk []byte } -func NewListMapper(cdc *wire.Codec, key StoreKey) ListMapper { +func NewListMapper(cdc *wire.Codec, key sdk.StoreKey) ListMapper { lk, err := cdc.MarshalBinary(int64(-1)) if err != nil { panic(err) @@ -32,7 +33,7 @@ func NewListMapper(cdc *wire.Codec, key StoreKey) ListMapper { } } -func (lm listMapper) Len(ctx Context) int64 { +func (lm listMapper) Len(ctx sdk.Context) int64 { store := ctx.KVStore(lm.key) bz := store.Get(lm.lk) if bz == nil { @@ -50,7 +51,7 @@ func (lm listMapper) Len(ctx Context) int64 { return res } -func (lm listMapper) Get(ctx Context, index int64, ptr interface{}) { +func (lm listMapper) Get(ctx sdk.Context, index int64, ptr interface{}) { if index < 0 { panic(errors.New("")) } @@ -61,7 +62,7 @@ func (lm listMapper) Get(ctx Context, index int64, ptr interface{}) { } } -func (lm listMapper) Set(ctx Context, index int64, value interface{}) { +func (lm listMapper) Set(ctx sdk.Context, index int64, value interface{}) { if index < 0 { panic(errors.New("")) } @@ -73,7 +74,7 @@ func (lm listMapper) Set(ctx Context, index int64, value interface{}) { store.Set(marshalInt64(lm.cdc, index), bz) } -func (lm listMapper) Push(ctx Context, value interface{}) { +func (lm listMapper) Push(ctx sdk.Context, value interface{}) { length := lm.Len(ctx) lm.Set(ctx, length, value) @@ -81,7 +82,7 @@ func (lm listMapper) Push(ctx Context, value interface{}) { store.Set(lm.lk, marshalInt64(lm.cdc, length+1)) } -func (lm listMapper) Iterate(ctx Context, ptr interface{}, fn func(Context, int64)) { +func (lm listMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context, int64)) { length := lm.Len(ctx) for i := int64(0); i < length; i++ { lm.Get(ctx, i, ptr) @@ -90,20 +91,20 @@ func (lm listMapper) Iterate(ctx Context, ptr interface{}, fn func(Context, int6 } type QueueMapper interface { - Push(Context, interface{}) - Peek(Context, interface{}) - Pop(Context) - IsEmpty(Context) bool - Iterate(Context, interface{}, func(Context) bool) + Push(sdk.Context, interface{}) + Peek(sdk.Context, interface{}) + Pop(sdk.Context) + IsEmpty(sdk.Context) bool + Iterate(sdk.Context, interface{}, func(sdk.Context) bool) } type queueMapper struct { - key StoreKey + key sdk.StoreKey cdc *wire.Codec ik []byte } -func NewQueueMapper(cdc *wire.Codec, key StoreKey) QueueMapper { +func NewQueueMapper(cdc *wire.Codec, key sdk.StoreKey) QueueMapper { ik, err := cdc.MarshalBinary(int64(-1)) if err != nil { panic(err) @@ -132,7 +133,7 @@ func (info queueInfo) isEmpty() bool { return info.Begin == info.End } -func (qm queueMapper) getQueueInfo(store KVStore) queueInfo { +func (qm queueMapper) getQueueInfo(store sdk.KVStore) queueInfo { bz := store.Get(qm.ik) if bz == nil { store.Set(qm.ik, marshalQueueInfo(qm.cdc, queueInfo{0, 0})) @@ -148,7 +149,7 @@ func (qm queueMapper) getQueueInfo(store KVStore) queueInfo { return info } -func (qm queueMapper) setQueueInfo(store KVStore, info queueInfo) { +func (qm queueMapper) setQueueInfo(store sdk.KVStore, info queueInfo) { bz, err := qm.cdc.MarshalBinary(info) if err != nil { panic(err) @@ -156,7 +157,7 @@ func (qm queueMapper) setQueueInfo(store KVStore, info queueInfo) { store.Set(qm.ik, bz) } -func (qm queueMapper) Push(ctx Context, value interface{}) { +func (qm queueMapper) Push(ctx sdk.Context, value interface{}) { store := ctx.KVStore(qm.key) info := qm.getQueueInfo(store) @@ -170,7 +171,7 @@ func (qm queueMapper) Push(ctx Context, value interface{}) { qm.setQueueInfo(store, info) } -func (qm queueMapper) Peek(ctx Context, ptr interface{}) { +func (qm queueMapper) Peek(ctx sdk.Context, ptr interface{}) { store := ctx.KVStore(qm.key) info := qm.getQueueInfo(store) bz := store.Get(marshalInt64(qm.cdc, info.Begin)) @@ -179,7 +180,7 @@ func (qm queueMapper) Peek(ctx Context, ptr interface{}) { } } -func (qm queueMapper) Pop(ctx Context) { +func (qm queueMapper) Pop(ctx sdk.Context) { store := ctx.KVStore(qm.key) info := qm.getQueueInfo(store) store.Delete(marshalInt64(qm.cdc, info.Begin)) @@ -187,13 +188,13 @@ func (qm queueMapper) Pop(ctx Context) { qm.setQueueInfo(store, info) } -func (qm queueMapper) IsEmpty(ctx Context) bool { +func (qm queueMapper) IsEmpty(ctx sdk.Context) bool { store := ctx.KVStore(qm.key) info := qm.getQueueInfo(store) return info.isEmpty() } -func (qm queueMapper) Iterate(ctx Context, ptr interface{}, fn func(Context) bool) { +func (qm queueMapper) Iterate(ctx sdk.Context, ptr interface{}, fn func(sdk.Context) bool) { store := ctx.KVStore(qm.key) info := qm.getQueueInfo(store) diff --git a/tests/stdlib_test.go b/types/stdlib/stdlib_test.go similarity index 94% rename from tests/stdlib_test.go rename to types/stdlib/stdlib_test.go index c2d47f6b52..7c871ccf3c 100644 --- a/tests/stdlib_test.go +++ b/types/stdlib/stdlib_test.go @@ -32,7 +32,7 @@ func defaultComponents(key sdk.StoreKey) (sdk.Context, *wire.Codec) { func TestListMapper(t *testing.T) { key := sdk.NewKVStoreKey("list") ctx, cdc := defaultComponents(key) - lm := sdk.NewListMapper(cdc, key) + lm := NewListMapper(cdc, key) val := S{1, true} var res S @@ -51,7 +51,7 @@ func TestListMapper(t *testing.T) { func TestQueueMapper(t *testing.T) { key := sdk.NewKVStoreKey("queue") ctx, cdc := defaultComponents(key) - qm := sdk.NewQueueMapper(cdc, key) + qm := NewQueueMapper(cdc, key) val := S{1, true} var res S From aee516f595cdec6ccbe475a1ee368ddbf44eb9eb Mon Sep 17 00:00:00 2001 From: Yukai Tu Date: Fri, 16 Mar 2018 17:57:28 -0700 Subject: [PATCH 05/64] Fix auth handler public key issue --- x/auth/ante.go | 13 +++++-- x/auth/ante_test.go | 94 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 99 insertions(+), 8 deletions(-) diff --git a/x/auth/ante.go b/x/auth/ante.go index 48e2344c6b..f2495af78d 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -2,6 +2,7 @@ package auth import ( "fmt" + "reflect" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -88,17 +89,21 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk // Check and possibly set pubkey. pubKey := acc.GetPubKey() if pubKey.Empty() { + if sig.PubKey.Empty() { + return nil, sdk.ErrInternal("public Key not found").Result() + } + if !reflect.DeepEqual(sig.PubKey.Address(), addr) { + return nil, sdk.ErrInternal( + fmt.Sprintf("invalid PubKey for address %v", addr)).Result() + } pubKey = sig.PubKey err := acc.SetPubKey(pubKey) if err != nil { return nil, sdk.ErrInternal("setting PubKey on signer").Result() } } - // TODO: should we enforce pubKey == sig.PubKey ? - // If not, ppl can send useless PubKeys after first tx - // Check sig. - if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { + if !pubKey.VerifyBytes(signBytes, sig.Signature) { return nil, sdk.ErrUnauthorized("signature verification failed").Result() } diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 17ea204d31..c3df2199d6 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -1,6 +1,7 @@ package auth import ( + "reflect" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -17,7 +18,7 @@ type testMsg struct { func newTestMsg(addrs ...sdk.Address) *testMsg { return &testMsg{ - signBytes: []byte("some sign bytes"), + signBytes: []byte(addrs[0]), signers: addrs, } } @@ -56,6 +57,10 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64) sdk.Tx { signBytes := sdk.StdSignBytes(ctx.ChainID(), seqs, msg) + return newTestTxWithSignBytes(msg, privs, seqs, signBytes) +} + +func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, signBytes []byte) sdk.Tx { sigs := make([]sdk.StdSignature, len(privs)) for i, priv := range privs { sigs[i] = sdk.StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), Sequence: seqs[i]} @@ -78,7 +83,6 @@ func TestAnteHandlerSigErrors(t *testing.T) { // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1, addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{0, 0}) // test no signatures tx = newTestTx(ctx, msg, []crypto.PrivKey{}, []int64{}) @@ -155,9 +159,91 @@ func TestAnteHandlerSequences(t *testing.T) { } func TestAnteHandlerBadSignBytes(t *testing.T) { - // TODO: test various cases of bad sign bytes + // setup + ms, capKey := setupMultiStore() + mapper := NewAccountMapper(capKey, &BaseAccount{}) + anteHandler := NewAnteHandler(mapper) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) + + // keys and addresses + priv1, addr1 := privAndAddr() + priv2, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + mapper.SetAccount(ctx, acc2) + + var tx sdk.Tx + + // test good tx and signBytes + msg := newTestMsg(addr1) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + checkValidTx(t, anteHandler, ctx, tx) + + // test invalid chain_id + tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes("", []int64{1}, msg)) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) + // test wrong seqs + tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes(ctx.ChainID(), []int64{2}, msg)) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) + // test wrong msg + tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes(ctx.ChainID(), []int64{1}, newTestMsg(addr2))) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) + + // test wrong signer if public key exist + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) + + // test wrong signer if public doesn't exist + msg = newTestMsg(addr2) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + } func TestAnteHandlerSetPubKey(t *testing.T) { - // TODO: test cases where pubkey is already set on the account + // setup + ms, capKey := setupMultiStore() + mapper := NewAccountMapper(capKey, &BaseAccount{}) + anteHandler := NewAnteHandler(mapper) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) + + // keys and addresses + priv1, addr1 := privAndAddr() + _, addr2 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + acc2 := mapper.NewAccountWithAddress(ctx, addr2) + mapper.SetAccount(ctx, acc2) + + var tx sdk.Tx + + // test good tx and set public key + msg := newTestMsg(addr1) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + checkValidTx(t, anteHandler, ctx, tx) + + acc1 = mapper.GetAccount(ctx, addr1) + reflect.DeepEqual(acc1.GetPubKey(), priv1.PubKey()) + + // test public key not found + msg = newTestMsg(addr2) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + sigs := tx.GetSignatures() + sigs[0].PubKey = crypto.PubKey{} + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + + acc2 = mapper.GetAccount(ctx, addr2) + assert.True(t, acc2.GetPubKey().Empty()) + + // test invalid signature and public key + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + + acc2 = mapper.GetAccount(ctx, addr2) + assert.True(t, acc2.GetPubKey().Empty()) } From 5965df16131c989f2c863e35f8bd0e281cc4eac0 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 17 Mar 2018 16:49:47 +0100 Subject: [PATCH 06/64] move signmsg into common sign and build functionality --- client/builder/builder.go | 15 ++++++++++++--- examples/basecoin/x/cool/commands/tx.go | 23 ++--------------------- x/bank/commands/sendtx.go | 12 +----------- 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/client/builder/builder.go b/client/builder/builder.go index 31e5189492..e252f40af8 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -88,7 +88,16 @@ func GetFromAddress() (from sdk.Address, err error) { } // sign and build the transaction from the msg -func SignAndBuild(signMsg sdk.StdSignMsg, cdc *wire.Codec) ([]byte, error) { +func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { + + // build the Sign Messsage from the Standard Message + chainID := viper.GetString(client.FlagChainID) + sequence := int64(viper.GetInt(client.FlagSequence)) + signMsg := sdk.StdSignMsg{ + ChainID: chainID, + Sequences: []int64{sequence}, + Msg: msg, + } keybase, err := keys.GetKeyBase() if err != nil { @@ -121,8 +130,8 @@ func SignAndBuild(signMsg sdk.StdSignMsg, cdc *wire.Codec) ([]byte, error) { } // sign and build the transaction from the msg -func SignBuildBroadcast(signMsg sdk.StdSignMsg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { - txBytes, err := SignAndBuild(signMsg, cdc) +func SignBuildBroadcast(msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { + txBytes, err := SignAndBuild(msg, cdc) if err != nil { return nil, err } diff --git a/examples/basecoin/x/cool/commands/tx.go b/examples/basecoin/x/cool/commands/tx.go index 71e80c98eb..2b16546806 100644 --- a/examples/basecoin/x/cool/commands/tx.go +++ b/examples/basecoin/x/cool/commands/tx.go @@ -5,11 +5,8 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool" @@ -33,17 +30,9 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { // create the message msg := cool.NewQuizMsg(from, args[0]) - chainID := viper.GetString(client.FlagChainID) - sequence := int64(viper.GetInt(client.FlagSequence)) - - signMsg := sdk.StdSignMsg{ - ChainID: chainID, - Sequences: []int64{sequence}, - Msg: msg, - } // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(signMsg, cdc) + res, err := builder.SignBuildBroadcast(msg, cdc) if err != nil { return err } @@ -72,17 +61,9 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { // create the message msg := cool.NewSetTrendMsg(from, args[0]) - chainID := viper.GetString(client.FlagChainID) - sequence := int64(viper.GetInt(client.FlagSequence)) - - signMsg := sdk.StdSignMsg{ - ChainID: chainID, - Sequences: []int64{sequence}, - Msg: msg, - } // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(signMsg, cdc) + res, err := builder.SignBuildBroadcast(msg, cdc) if err != nil { return err } diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 6ce0097a8e..7e6dd463dd 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -7,7 +7,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -51,17 +50,8 @@ func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { return err } - chainID := viper.GetString(client.FlagChainID) - sequence := int64(viper.GetInt(client.FlagSequence)) - - signMsg := sdk.StdSignMsg{ - ChainID: chainID, - Sequences: []int64{sequence}, - Msg: msg, - } - // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(signMsg, c.cdc) + res, err := builder.SignBuildBroadcast(msg, c.cdc) if err != nil { return err } From eb2380383306fa281926fbd2fea94f84ea62a7a9 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 13 Mar 2018 13:46:08 +0100 Subject: [PATCH 07/64] remove tx.GetFeePayer --- baseapp/baseapp_test.go | 1 - docs/guide.md | 13 +++---------- docs/sdk/overview.rst | 11 ----------- examples/kvstore/tx.go | 4 ---- mock/tx.go | 4 ---- types/tx_msg.go | 12 +++++++----- 6 files changed, 10 insertions(+), 35 deletions(-) diff --git a/baseapp/baseapp_test.go b/baseapp/baseapp_test.go index 5cc20185d4..de9a0253c5 100644 --- a/baseapp/baseapp_test.go +++ b/baseapp/baseapp_test.go @@ -328,7 +328,6 @@ func (tx testUpdatePowerTx) GetMsg() sdk.Msg { return tx func (tx testUpdatePowerTx) GetSignBytes() []byte { return nil } func (tx testUpdatePowerTx) ValidateBasic() sdk.Error { return nil } func (tx testUpdatePowerTx) GetSigners() []sdk.Address { return nil } -func (tx testUpdatePowerTx) GetFeePayer() sdk.Address { return nil } func (tx testUpdatePowerTx) GetSignatures() []sdk.StdSignature { return nil } func TestValidatorChange(t *testing.T) { diff --git a/docs/guide.md b/docs/guide.md index 1a6d21c82a..5c31d2e271 100644 --- a/docs/guide.md +++ b/docs/guide.md @@ -105,14 +105,6 @@ type Tx interface { GetMsg() Msg - // The address that pays the base fee for this message. The fee is - // deducted before the Msg is processed. - GetFeePayer() Address - - // Get the canonical byte representation of the Tx. - // Includes any signatures (or empty slots). - GetTxBytes() []byte - // Signatures returns the signature of signers who signed the Msg. // CONTRACT: Length returned is same as length of // pubkeys returned from MsgKeySigners, and the order @@ -148,8 +140,9 @@ case of Basecoin, the public key only needs to be included in the first transaction send by a given account - after that, the public key is forever stored by the application and can be left out of transactions. -Transactions can also specify the address responsible for paying the -transaction's fees using the `tx.GetFeePayer()` method. +The address responsible for paying the transactions fee is the first address +returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided +to return this. The standard way to create a transaction from a message is to use the `StdTx`: diff --git a/docs/sdk/overview.rst b/docs/sdk/overview.rst index bf7b23a606..9e79dd04ff 100644 --- a/docs/sdk/overview.rst +++ b/docs/sdk/overview.rst @@ -219,14 +219,6 @@ A transaction is a message with additional information for authentication: GetMsg() Msg - // The address that pays the base fee for this message. The fee is - // deducted before the Msg is processed. - GetFeePayer() Address - - // Get the canonical byte representation of the Tx. - // Includes any signatures (or empty slots). - GetTxBytes() []byte - // Signatures returns the signature of signers who signed the Msg. // CONTRACT: Length returned is same as length of // pubkeys returned from MsgKeySigners, and the order @@ -261,9 +253,6 @@ case of Basecoin, the public key only needs to be included in the first transaction send by a given account - after that, the public key is forever stored by the application and can be left out of transactions. -Transactions can also specify the address responsible for paying the -transaction's fees using the ``tx.GetFeePayer()`` method. - The standard way to create a transaction from a message is to use the ``StdTx``: :: diff --git a/examples/kvstore/tx.go b/examples/kvstore/tx.go index fdecf63807..c9c30c885d 100644 --- a/examples/kvstore/tx.go +++ b/examples/kvstore/tx.go @@ -51,10 +51,6 @@ func (tx kvstoreTx) GetSignatures() []sdk.StdSignature { return nil } -func (tx kvstoreTx) GetFeePayer() sdk.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) { diff --git a/mock/tx.go b/mock/tx.go index efe60feb91..326946eaa5 100644 --- a/mock/tx.go +++ b/mock/tx.go @@ -64,10 +64,6 @@ func (tx kvstoreTx) GetSignatures() []sdk.StdSignature { return nil } -func (tx kvstoreTx) GetFeePayer() sdk.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) { diff --git a/types/tx_msg.go b/types/tx_msg.go index 141f9d050d..8510237487 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -33,10 +33,6 @@ type Tx interface { // Gets the Msg. GetMsg() Msg - // The address that pays the base fee for this message. The fee is - // deducted before the Msg is processed. - GetFeePayer() Address - // Signatures returns the signature of signers who signed the Msg. // CONTRACT: Length returned is same as length of // pubkeys returned from MsgKeySigners, and the order @@ -63,9 +59,15 @@ func NewStdTx(msg Msg, sigs []StdSignature) StdTx { } } +// FeePayer returns the address responsible for paying the fees +// for the transactions. It's the first address returned by msg.GetSigners(). +// If GetSigners() is empty, this panics. +func FeePayer(tx Tx) Address { + return tx.GetMsg().GetSigners()[0] +} + //nolint func (tx StdTx) GetMsg() Msg { return tx.Msg } -func (tx StdTx) GetFeePayer() Address { return tx.Signatures[0].PubKey.Address() } // XXX but PubKey is optional! func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } // StdSignDoc is replay-prevention structure. From be7cb6c96c7a923be818894fef6eb94b9c8c2106 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 13 Mar 2018 13:50:21 +0100 Subject: [PATCH 08/64] types: introduce StdFee --- types/tx_msg.go | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/types/tx_msg.go b/types/tx_msg.go index 8510237487..b829bef961 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -49,6 +49,7 @@ var _ Tx = (*StdTx)(nil) // NOTE: the first signature is the FeePayer (Signatures must not be nil). type StdTx struct { Msg `json:"msg"` + Fee StdFee `json:"fee"` Signatures []StdSignature `json:"signatures"` } @@ -59,6 +60,16 @@ func NewStdTx(msg Msg, sigs []StdSignature) StdTx { } } +// SetFee sets the StdFee on the transaction. +func (tx StdTx) SetFee(fee StdFee) StdTx { + tx.Fee = fee + return tx +} + +//nolint +func (tx StdTx) GetMsg() Msg { return tx.Msg } +func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } + // FeePayer returns the address responsible for paying the fees // for the transactions. It's the first address returned by msg.GetSigners(). // If GetSigners() is empty, this panics. @@ -66,9 +77,20 @@ func FeePayer(tx Tx) Address { return tx.GetMsg().GetSigners()[0] } -//nolint -func (tx StdTx) GetMsg() Msg { return tx.Msg } -func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } +// StdFee includes the amount of coins paid in fees and the maximum +// gas to be used by the transaction. The ratio yields an effectie "gasprice", +// which must be above some miminum to be accepted into the mempool. +type StdFee struct { + Amount Coins `json"amount"` + Gas int64 `json"gas"` +} + +func NewStdFee(gas int64, amount ...Coin) StdFee { + return StdFee{ + Amount: amount, + Gas: gas, + } +} // StdSignDoc is replay-prevention structure. // It includes the result of msg.GetSignBytes(), From 45f8ccbe90bed049ae6e0544e9b2320ce554199d Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 14 Mar 2018 18:16:52 +0100 Subject: [PATCH 09/64] x/auth: cleanup ante handler --- types/errors.go | 6 ++++++ types/tx_msg.go | 2 +- x/auth/ante.go | 45 ++++++++++++++++++++++++--------------------- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/types/errors.go b/types/errors.go index 9d8175e309..008bd6f086 100644 --- a/types/errors.go +++ b/types/errors.go @@ -27,6 +27,7 @@ const ( CodeInsufficientFunds CodeType = 5 CodeUnknownRequest CodeType = 6 CodeUnrecognizedAddress CodeType = 7 + CodeMissingPubKey CodeType = 8 CodeGenesisParse CodeType = 0xdead // TODO: remove ? ) @@ -50,6 +51,8 @@ func CodeToDefaultMsg(code CodeType) string { return "Unknown request" case CodeUnrecognizedAddress: return "Unrecognized address" + case CodeMissingPubKey: + return "Missing pubkey" default: return fmt.Sprintf("Unknown code %d", code) } @@ -84,6 +87,9 @@ func ErrUnknownRequest(msg string) Error { func ErrUnrecognizedAddress(addr Address) Error { return newError(CodeUnrecognizedAddress, addr.String()) } +func ErrMissingPubKey(addr Address) Error { + return newError(CodeMissingPubKey, addr.String()) +} //---------------------------------------- // Error & sdkError diff --git a/types/tx_msg.go b/types/tx_msg.go index b829bef961..81719f18a7 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -78,7 +78,7 @@ func FeePayer(tx Tx) Address { } // StdFee includes the amount of coins paid in fees and the maximum -// gas to be used by the transaction. The ratio yields an effectie "gasprice", +// gas to be used by the transaction. The ratio yields an effective "gasprice", // which must be above some miminum to be accepted into the mempool. type StdFee struct { Amount Coins `json"amount"` diff --git a/x/auth/ante.go b/x/auth/ante.go index f2495af78d..f602f8911b 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -7,6 +7,9 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// NewAnteHandler returns an AnteHandler that checks +// and increments sequence numbers, checks signatures, +// and deducts fees from the first signer. func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { return func( ctx sdk.Context, tx sdk.Tx, @@ -31,32 +34,20 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { true } - // Collect accounts to set in the context - var signerAccs = make([]sdk.Account, len(signerAddrs)) - - // Get the sign bytes by collecting all sequence numbers + // Get the sign bytes (requires all sequence numbers) sequences := make([]int64, len(signerAddrs)) for i := 0; i < len(signerAddrs); i++ { sequences[i] = sigs[i].Sequence } signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, msg) - // Check fee payer sig and nonce, and deduct fee. - // This is done first because it only - // requires fetching 1 account. - payerAddr, payerSig := signerAddrs[0], sigs[0] - payerAcc, res := processSig(ctx, accountMapper, payerAddr, payerSig, signBytes) - if !res.IsOK() { - return ctx, res, true - } - signerAccs[0] = payerAcc - // TODO: Charge fee from payerAcc. - // TODO: accountMapper.SetAccount(ctx, payerAddr) + // Check sig and nonce and collect signer accounts. + var signerAccs = make([]sdk.Account, len(signerAddrs)) + for i := 0; i < len(sigs); i++ { + isFeePayer := i == 0 // first sig pays the fees - // Check sig and nonce for the rest. - for i := 1; i < len(sigs); i++ { signerAddr, sig := signerAddrs[i], sigs[i] - signerAcc, res := processSig(ctx, accountMapper, signerAddr, sig, signBytes) + signerAcc, res := processSig(ctx, accountMapper, signerAddr, sig, signBytes, isFeePayer) if !res.IsOK() { return ctx, res, true } @@ -64,13 +55,17 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { } ctx = WithSigners(ctx, signerAccs) + // TODO: tx tags (?) return ctx, sdk.Result{}, false // continue... } } // verify the signature and increment the sequence. -// if the account doesn't have a pubkey, set it as well. -func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk.StdSignature, signBytes []byte) (acc sdk.Account, res sdk.Result) { +// if the account doesn't have a pubkey, set it. +// deduct fee from fee payer. +func processSig(ctx sdk.Context, am sdk.AccountMapper, + addr sdk.Address, sig sdk.StdSignature, signBytes []byte, + isFeePayer bool) (acc sdk.Account, res sdk.Result) { // Get the account acc = am.GetAccount(ctx, addr) @@ -86,7 +81,8 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk } acc.SetSequence(seq + 1) - // Check and possibly set pubkey. + // If pubkey is not known for account, + // set it from the StdSignature pubKey := acc.GetPubKey() if pubKey.Empty() { if sig.PubKey.Empty() { @@ -97,6 +93,9 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk fmt.Sprintf("invalid PubKey for address %v", addr)).Result() } pubKey = sig.PubKey + if pubKey.Empty() { + return nil, sdk.ErrMissingPubKey(addr).Result() + } err := acc.SetPubKey(pubKey) if err != nil { return nil, sdk.ErrInternal("setting PubKey on signer").Result() @@ -107,6 +106,10 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk return nil, sdk.ErrUnauthorized("signature verification failed").Result() } + if isFeePayer { + // TODO: pay fees + } + // Save the account. am.SetAccount(ctx, acc) return From 1b16f0c68429518b07ab9f28edad3caf727b7197 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 15 Mar 2018 17:37:16 +0100 Subject: [PATCH 10/64] Deduct fee from fee payer's account balance --- x/auth/ante.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/x/auth/ante.go b/x/auth/ante.go index f602f8911b..724b7c5ef6 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -26,6 +26,12 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { // TODO: can tx just implement message? msg := tx.GetMsg() + // TODO: will this always be a stdtx? should that be used in the function signature? + stdTx, ok := tx.(sdk.StdTx) + if !ok { + return ctx, sdk.ErrInternal("tx must be sdk.StdTx").Result(), true + } + // Assert that number of signatures is correct. var signerAddrs = msg.GetSigners() if len(sigs) != len(signerAddrs) { @@ -47,7 +53,8 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { isFeePayer := i == 0 // first sig pays the fees signerAddr, sig := signerAddrs[i], sigs[i] - signerAcc, res := processSig(ctx, accountMapper, signerAddr, sig, signBytes, isFeePayer) + signerAcc, res := processSig(ctx, accountMapper, signerAddr, sig, + signBytes, isFeePayer, stdTx.Fee.Amount) if !res.IsOK() { return ctx, res, true } @@ -65,7 +72,7 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { // deduct fee from fee payer. func processSig(ctx sdk.Context, am sdk.AccountMapper, addr sdk.Address, sig sdk.StdSignature, signBytes []byte, - isFeePayer bool) (acc sdk.Account, res sdk.Result) { + isFeePayer bool, feeAmount sdk.Coins) (acc sdk.Account, res sdk.Result) { // Get the account acc = am.GetAccount(ctx, addr) @@ -106,8 +113,16 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, return nil, sdk.ErrUnauthorized("signature verification failed").Result() } + // If this is the fee payer, deduct the fee. if isFeePayer { - // TODO: pay fees + coins := acc.GetCoins() + newCoins := coins.Minus(feeAmount) + if !newCoins.IsNotNegative() { + errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) + return nil, sdk.ErrInsufficientFunds(errMsg).Result() + } + + acc.SetCoins(newCoins) } // Save the account. From 6dc46064cb7e0ac678769e8ffb38aec6a1bd75a6 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Thu, 15 Mar 2018 17:37:39 +0100 Subject: [PATCH 11/64] Started on fee tests --- x/auth/ante_test.go | 50 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index c3df2199d6..9523cdb39e 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -55,7 +55,7 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, assert.Equal(t, code, result.Code) } -func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64) sdk.Tx { +func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, feeAmount int64) sdk.Tx { signBytes := sdk.StdSignBytes(ctx.ChainID(), seqs, msg) return newTestTxWithSignBytes(msg, privs, seqs, signBytes) } @@ -65,7 +65,9 @@ func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, s for i, priv := range privs { sigs[i] = sdk.StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), Sequence: seqs[i]} } - return sdk.NewStdTx(msg, sigs) + tx := sdk.NewStdTx(msg, sigs) + tx.SetFee(sdk.StdFee{Gas: 0, Amount: sdk.Coins{sdk.Coin{Amount: feeAmount, Denom: "atom"}}}) + return tx } // Test various error cases in the AnteHandler control flow. @@ -85,15 +87,15 @@ func TestAnteHandlerSigErrors(t *testing.T) { msg := newTestMsg(addr1, addr2) // test no signatures - tx = newTestTx(ctx, msg, []crypto.PrivKey{}, []int64{}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{}, []int64{}, int64(0)) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test num sigs dont match GetSigners - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(0)) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test an unrecognized account - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{0, 0}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{0, 0}, int64(0)) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnrecognizedAddress) // save the first account, but second is still unrecognized @@ -123,7 +125,7 @@ func TestAnteHandlerSequences(t *testing.T) { // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(0)) // test good tx from one signer checkValidTx(t, anteHandler, ctx, tx) @@ -132,12 +134,12 @@ func TestAnteHandlerSequences(t *testing.T) { checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix sequence, should pass - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{1}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{1}, int64(0)) checkValidTx(t, anteHandler, ctx, tx) // new tx with another signer and correct sequences msg = newTestMsg(addr1, addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{2, 0}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{2, 0}, int64(0)) checkValidTx(t, anteHandler, ctx, tx) // replay fails @@ -145,19 +147,45 @@ func TestAnteHandlerSequences(t *testing.T) { // tx from just second signer with incorrect sequence fails msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{0}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{0}, int64(0)) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix the sequence and it passes - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, int64(0)) checkValidTx(t, anteHandler, ctx, tx) // another tx from both of them that passes msg = newTestMsg(addr1, addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{3, 2}) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{3, 2}, int64(0)) checkValidTx(t, anteHandler, ctx, tx) } +// Test logic around fee deduction. +func TestAnteHandlerFees(t *testing.T) { + // // setup + // ms, capKey := setupMultiStore() + // mapper := NewAccountMapper(capKey, &BaseAccount{}) + // anteHandler := NewAnteHandler(mapper) + // ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) + // + // // keys and addresses + // priv1, addr1 := privAndAddr() + // priv2, addr2 := privAndAddr() + // + // // set the accounts + // acc1 := mapper.NewAccountWithAddress(ctx, addr1) + // mapper.SetAccount(ctx, acc1) + // acc2 := mapper.NewAccountWithAddress(ctx, addr2) + // mapper.SetAccount(ctx, acc2) + // + // // msg and signatures + // var tx sdk.Tx + // msg := newTestMsg(addr1) + // tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(1)) + + // TODO +} + func TestAnteHandlerBadSignBytes(t *testing.T) { // setup ms, capKey := setupMultiStore() From 3babf8c2d96f5a49d9864f2c6e3d0436f10368c1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 19:54:18 +0100 Subject: [PATCH 12/64] fix and clean fees and x/auth --- client/builder/builder.go | 2 +- examples/basecoin/app/app_test.go | 21 +++-- examples/basecoin/x/cool/types.go | 4 +- types/errors.go | 12 +-- types/tx_msg.go | 61 ++++++++------ x/auth/ante.go | 87 ++++++++++--------- x/auth/ante_test.go | 134 +++++++++++++++++++++--------- x/bank/mapper.go | 2 +- 8 files changed, 204 insertions(+), 119 deletions(-) diff --git a/client/builder/builder.go b/client/builder/builder.go index e252f40af8..a64dfeda74 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -124,7 +124,7 @@ func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { }} // marshal bytes - tx := sdk.NewStdTx(signMsg.Msg, sigs) + tx := sdk.NewStdTx(signMsg.Msg, signMsg.Fee, sigs) return cdc.MarshalBinary(tx) } diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index c2ef3e4547..4c578579ba 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -29,6 +29,10 @@ var ( addr1 = priv1.PubKey().Address() addr2 = crypto.GenPrivKeyEd25519().PubKey().Address() coins = sdk.Coins{{"foocoin", 10}} + fee = sdk.StdFee{ + sdk.Coins{{"foocoin", 0}}, + 0, + } sendMsg = bank.SendMsg{ Inputs: []bank.Input{bank.NewInput(addr1, coins)}, @@ -82,8 +86,8 @@ func TestMsgs(t *testing.T) { sequences := []int64{0} for i, m := range msgs { - sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, m.msg)) - tx := sdk.NewStdTx(m.msg, []sdk.StdSignature{{ + sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, m.msg)) + tx := sdk.NewStdTx(m.msg, fee, []sdk.StdSignature{{ PubKey: priv1.PubKey(), Signature: sig, }}) @@ -180,8 +184,8 @@ func TestSendMsgWithAccounts(t *testing.T) { // Sign the tx sequences := []int64{0} - sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, sendMsg)) - tx := sdk.NewStdTx(sendMsg, []sdk.StdSignature{{ + sig := priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, sendMsg)) + tx := sdk.NewStdTx(sendMsg, fee, []sdk.StdSignature{{ PubKey: priv1.PubKey(), Signature: sig, }}) @@ -213,7 +217,7 @@ func TestSendMsgWithAccounts(t *testing.T) { // resigning the tx with the bumped sequence should work sequences = []int64{1} - sig = priv1.Sign(sdk.StdSignBytes(chainID, sequences, tx.Msg)) + sig = priv1.Sign(sdk.StdSignBytes(chainID, sequences, fee, tx.Msg)) tx.Signatures[0].Signature = sig res = bapp.Deliver(tx) assert.Equal(t, sdk.CodeOK, res.Code, res.Log) @@ -269,10 +273,13 @@ func TestQuizMsg(t *testing.T) { func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, expPass bool) { + // TODO: + var fee sdk.StdFee + // Sign the tx - tx := sdk.NewStdTx(msg, []sdk.StdSignature{{ + tx := sdk.NewStdTx(msg, fee, []sdk.StdSignature{{ PubKey: priv1.PubKey(), - Signature: priv1.Sign(sdk.StdSignBytes(chainID, []int64{seq}, msg)), + Signature: priv1.Sign(sdk.StdSignBytes(chainID, []int64{seq}, fee, msg)), Sequence: seq, }}) diff --git a/examples/basecoin/x/cool/types.go b/examples/basecoin/x/cool/types.go index f721bfa19b..10515c8abe 100644 --- a/examples/basecoin/x/cool/types.go +++ b/examples/basecoin/x/cool/types.go @@ -37,7 +37,7 @@ func (msg SetTrendMsg) String() string { // Validate Basic is used to quickly disqualify obviously invalid messages quickly func (msg SetTrendMsg) ValidateBasic() sdk.Error { if len(msg.Sender) == 0 { - return sdk.ErrUnrecognizedAddress(msg.Sender).Trace("") + return sdk.ErrUnrecognizedAddress(msg.Sender.String()).Trace("") } if strings.Contains(msg.Cool, "hot") { return sdk.ErrUnauthorized("").Trace("hot is not cool") @@ -88,7 +88,7 @@ func (msg QuizMsg) String() string { // Validate Basic is used to quickly disqualify obviously invalid messages quickly func (msg QuizMsg) ValidateBasic() sdk.Error { if len(msg.Sender) == 0 { - return sdk.ErrUnrecognizedAddress(msg.Sender).Trace("") + return sdk.ErrUnrecognizedAddress(msg.Sender.String()).Trace("") } return nil } diff --git a/types/errors.go b/types/errors.go index 008bd6f086..5c96d8c241 100644 --- a/types/errors.go +++ b/types/errors.go @@ -27,7 +27,7 @@ const ( CodeInsufficientFunds CodeType = 5 CodeUnknownRequest CodeType = 6 CodeUnrecognizedAddress CodeType = 7 - CodeMissingPubKey CodeType = 8 + CodeInvalidPubKey CodeType = 8 CodeGenesisParse CodeType = 0xdead // TODO: remove ? ) @@ -51,7 +51,7 @@ func CodeToDefaultMsg(code CodeType) string { return "Unknown request" case CodeUnrecognizedAddress: return "Unrecognized address" - case CodeMissingPubKey: + case CodeInvalidPubKey: return "Missing pubkey" default: return fmt.Sprintf("Unknown code %d", code) @@ -84,11 +84,11 @@ func ErrInsufficientFunds(msg string) Error { func ErrUnknownRequest(msg string) Error { return newError(CodeUnknownRequest, msg) } -func ErrUnrecognizedAddress(addr Address) Error { - return newError(CodeUnrecognizedAddress, addr.String()) +func ErrUnrecognizedAddress(msg string) Error { + return newError(CodeUnrecognizedAddress, msg) } -func ErrMissingPubKey(addr Address) Error { - return newError(CodeMissingPubKey, addr.String()) +func ErrInvalidPubKey(msg string) Error { + return newError(CodeInvalidPubKey, msg) } //---------------------------------------- diff --git a/types/tx_msg.go b/types/tx_msg.go index 81719f18a7..b41d9879a9 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -45,7 +45,7 @@ type Tx interface { var _ Tx = (*StdTx)(nil) -// StdTx is a standard way to wrap a Msg with Signatures. +// StdTx is a standard way to wrap a Msg with Fee and Signatures. // NOTE: the first signature is the FeePayer (Signatures must not be nil). type StdTx struct { Msg `json:"msg"` @@ -53,19 +53,14 @@ type StdTx struct { Signatures []StdSignature `json:"signatures"` } -func NewStdTx(msg Msg, sigs []StdSignature) StdTx { +func NewStdTx(msg Msg, fee StdFee, sigs []StdSignature) StdTx { return StdTx{ Msg: msg, + Fee: fee, Signatures: sigs, } } -// SetFee sets the StdFee on the transaction. -func (tx StdTx) SetFee(fee StdFee) StdTx { - tx.Fee = fee - return tx -} - //nolint func (tx StdTx) GetMsg() Msg { return tx.Msg } func (tx StdTx) GetSignatures() []StdSignature { return tx.Signatures } @@ -77,6 +72,8 @@ func FeePayer(tx Tx) Address { return tx.GetMsg().GetSigners()[0] } +//__________________________________________________________ + // StdFee includes the amount of coins paid in fees and the maximum // gas to be used by the transaction. The ratio yields an effective "gasprice", // which must be above some miminum to be accepted into the mempool. @@ -92,6 +89,16 @@ func NewStdFee(gas int64, amount ...Coin) StdFee { } } +func (fee StdFee) Bytes() []byte { + bz, err := json.Marshal(fee) // TODO + if err != nil { + panic(err) + } + return bz +} + +//__________________________________________________________ + // StdSignDoc is replay-prevention structure. // It includes the result of msg.GetSignBytes(), // as well as the ChainID (prevent cross chain replay) @@ -100,27 +107,18 @@ func NewStdFee(gas int64, amount ...Coin) StdFee { type StdSignDoc struct { ChainID string `json:"chain_id"` Sequences []int64 `json:"sequences"` + FeeBytes []byte `json:"fee_bytes"` MsgBytes []byte `json:"msg_bytes"` - AltBytes []byte `json:"alt_bytes"` // TODO: do we really want this ? + AltBytes []byte `json:"alt_bytes"` } -// StdSignMsg is a convenience structure for passing along -// a Msg with the other requirements for a StdSignDoc before -// it is signed. For use in the CLI -type StdSignMsg struct { - ChainID string - Sequences []int64 - Msg Msg -} - -func (msg StdSignMsg) Bytes() []byte { - return StdSignBytes(msg.ChainID, msg.Sequences, msg.Msg) -} - -func StdSignBytes(chainID string, sequences []int64, msg Msg) []byte { +// StdSignBytes returns the bytes to sign for a transaction. +// TODO: change the API to just take a chainID and StdTx ? +func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg Msg) []byte { bz, err := json.Marshal(StdSignDoc{ ChainID: chainID, Sequences: sequences, + FeeBytes: fee.Bytes(), MsgBytes: msg.GetSignBytes(), }) if err != nil { @@ -129,7 +127,22 @@ func StdSignBytes(chainID string, sequences []int64, msg Msg) []byte { return bz } -//------------------------------------- +// StdSignMsg is a convenience structure for passing along +// a Msg with the other requirements for a StdSignDoc before +// it is signed. For use in the CLI. +type StdSignMsg struct { + ChainID string + Sequences []int64 + Fee StdFee + Msg Msg + // XXX: Alt +} + +func (msg StdSignMsg) Bytes() []byte { + return StdSignBytes(msg.ChainID, msg.Sequences, msg.Fee, msg.Msg) +} + +//__________________________________________________________ // Application function variable used to unmarshal transaction bytes type TxDecoder func(txBytes []byte) (Tx, Error) diff --git a/x/auth/ante.go b/x/auth/ante.go index 724b7c5ef6..4305929b97 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -1,8 +1,8 @@ package auth import ( + "bytes" "fmt" - "reflect" sdk "github.com/cosmos/cosmos-sdk/types" ) @@ -40,44 +40,61 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { true } - // Get the sign bytes (requires all sequence numbers) + // Get the sign bytes (requires all sequence numbers and the fee) sequences := make([]int64, len(signerAddrs)) for i := 0; i < len(signerAddrs); i++ { sequences[i] = sigs[i].Sequence } - signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, msg) + fee := stdTx.Fee + signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, fee, msg) // Check sig and nonce and collect signer accounts. var signerAccs = make([]sdk.Account, len(signerAddrs)) for i := 0; i < len(sigs); i++ { - isFeePayer := i == 0 // first sig pays the fees - signerAddr, sig := signerAddrs[i], sigs[i] - signerAcc, res := processSig(ctx, accountMapper, signerAddr, sig, - signBytes, isFeePayer, stdTx.Fee.Amount) + + // check signature, return account with incremented nonce + signerAcc, res := processSig( + ctx, accountMapper, + signerAddr, sig, signBytes, + ) if !res.IsOK() { return ctx, res, true } + + // first sig pays the fees + if i == 0 { + signerAcc, res = deductFees(signerAcc, fee) + if !res.IsOK() { + return ctx, res, true + } + } + + // Save the account. + accountMapper.SetAccount(ctx, signerAcc) signerAccs[i] = signerAcc } + // cache the signer accounts in the context ctx = WithSigners(ctx, signerAccs) + // TODO: tx tags (?) + return ctx, sdk.Result{}, false // continue... } } // verify the signature and increment the sequence. // if the account doesn't have a pubkey, set it. -// deduct fee from fee payer. -func processSig(ctx sdk.Context, am sdk.AccountMapper, - addr sdk.Address, sig sdk.StdSignature, signBytes []byte, - isFeePayer bool, feeAmount sdk.Coins) (acc sdk.Account, res sdk.Result) { +func processSig( + ctx sdk.Context, am sdk.AccountMapper, + addr sdk.Address, sig sdk.StdSignature, signBytes []byte) ( + acc sdk.Account, res sdk.Result) { - // Get the account + // Get the account. acc = am.GetAccount(ctx, addr) if acc == nil { - return nil, sdk.ErrUnrecognizedAddress(addr).Result() + return nil, sdk.ErrUnrecognizedAddress(addr.String()).Result() } // Check and increment sequence number. @@ -89,23 +106,20 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, acc.SetSequence(seq + 1) // If pubkey is not known for account, - // set it from the StdSignature + // set it from the StdSignature. pubKey := acc.GetPubKey() if pubKey.Empty() { - if sig.PubKey.Empty() { - return nil, sdk.ErrInternal("public Key not found").Result() - } - if !reflect.DeepEqual(sig.PubKey.Address(), addr) { - return nil, sdk.ErrInternal( - fmt.Sprintf("invalid PubKey for address %v", addr)).Result() - } pubKey = sig.PubKey if pubKey.Empty() { - return nil, sdk.ErrMissingPubKey(addr).Result() + return nil, sdk.ErrInvalidPubKey("PubKey not found").Result() + } + if !bytes.Equal(pubKey.Address(), addr) { + return nil, sdk.ErrInvalidPubKey( + fmt.Sprintf("PubKey does not match Signer address %v", addr)).Result() } err := acc.SetPubKey(pubKey) if err != nil { - return nil, sdk.ErrInternal("setting PubKey on signer").Result() + return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() } } // Check sig. @@ -113,19 +127,18 @@ func processSig(ctx sdk.Context, am sdk.AccountMapper, return nil, sdk.ErrUnauthorized("signature verification failed").Result() } - // If this is the fee payer, deduct the fee. - if isFeePayer { - coins := acc.GetCoins() - newCoins := coins.Minus(feeAmount) - if !newCoins.IsNotNegative() { - errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) - return nil, sdk.ErrInsufficientFunds(errMsg).Result() - } - - acc.SetCoins(newCoins) - } - - // Save the account. - am.SetAccount(ctx, acc) return } + +// deduct the fee from the account +func deductFees(acc sdk.Account, fee sdk.StdFee) (sdk.Account, sdk.Result) { + coins := acc.GetCoins() + feeAmount := fee.Amount + newCoins := coins.Minus(feeAmount) + if !newCoins.IsNotNegative() { + errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) + return nil, sdk.ErrInsufficientFunds(errMsg).Result() + } + acc.SetCoins(newCoins) + return acc, sdk.Result{} +} diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index 9523cdb39e..cecc4a1418 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -1,38 +1,54 @@ package auth import ( - "reflect" + "encoding/json" "testing" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" crypto "github.com/tendermint/go-crypto" ) // msg type for testing type testMsg struct { - signBytes []byte - signers []sdk.Address + signers []sdk.Address } func newTestMsg(addrs ...sdk.Address) *testMsg { return &testMsg{ - signBytes: []byte(addrs[0]), - signers: addrs, + signers: addrs, } } func (msg *testMsg) Type() string { return "testMsg" } func (msg *testMsg) Get(key interface{}) (value interface{}) { return nil } func (msg *testMsg) GetSignBytes() []byte { - return msg.signBytes + bz, err := json.Marshal(msg.signers) + if err != nil { + panic(err) + } + return bz } func (msg *testMsg) ValidateBasic() sdk.Error { return nil } func (msg *testMsg) GetSigners() []sdk.Address { return msg.signers } +func newStdFee() sdk.StdFee { + return sdk.NewStdFee(100, + sdk.Coin{"atom", 150}, + ) +} + +// coins to more than cover the fee +func newCoins() sdk.Coins { + return sdk.Coins{ + {"atom", 10000000}, + } +} + // generate a priv key and return it with its address func privAndAddr() (crypto.PrivKey, sdk.Address) { priv := crypto.GenPrivKeyEd25519() @@ -55,18 +71,17 @@ func checkInvalidTx(t *testing.T, anteHandler sdk.AnteHandler, ctx sdk.Context, assert.Equal(t, code, result.Code) } -func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, feeAmount int64) sdk.Tx { - signBytes := sdk.StdSignBytes(ctx.ChainID(), seqs, msg) - return newTestTxWithSignBytes(msg, privs, seqs, signBytes) +func newTestTx(ctx sdk.Context, msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee sdk.StdFee) sdk.Tx { + signBytes := sdk.StdSignBytes(ctx.ChainID(), seqs, fee, msg) + return newTestTxWithSignBytes(msg, privs, seqs, fee, signBytes) } -func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, signBytes []byte) sdk.Tx { +func newTestTxWithSignBytes(msg sdk.Msg, privs []crypto.PrivKey, seqs []int64, fee sdk.StdFee, signBytes []byte) sdk.Tx { sigs := make([]sdk.StdSignature, len(privs)) for i, priv := range privs { sigs[i] = sdk.StdSignature{PubKey: priv.PubKey(), Signature: priv.Sign(signBytes), Sequence: seqs[i]} } - tx := sdk.NewStdTx(msg, sigs) - tx.SetFee(sdk.StdFee{Gas: 0, Amount: sdk.Coins{sdk.Coin{Amount: feeAmount, Denom: "atom"}}}) + tx := sdk.NewStdTx(msg, fee, sigs) return tx } @@ -85,21 +100,26 @@ func TestAnteHandlerSigErrors(t *testing.T) { // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1, addr2) + fee := newStdFee() // test no signatures - tx = newTestTx(ctx, msg, []crypto.PrivKey{}, []int64{}, int64(0)) + privs, seqs := []crypto.PrivKey{}, []int64{} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test num sigs dont match GetSigners - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(0)) + privs, seqs = []crypto.PrivKey{priv1}, []int64{0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test an unrecognized account - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{0, 0}, int64(0)) + privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{0, 0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnrecognizedAddress) // save the first account, but second is still unrecognized acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(fee.Amount) mapper.SetAccount(ctx, acc1) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnrecognizedAddress) } @@ -118,28 +138,34 @@ func TestAnteHandlerSequences(t *testing.T) { // set the accounts acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) mapper.SetAccount(ctx, acc1) acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) mapper.SetAccount(ctx, acc2) // msg and signatures var tx sdk.Tx msg := newTestMsg(addr1) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(0)) + fee := newStdFee() // test good tx from one signer + privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // test sending it again fails (replay protection) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix sequence, should pass - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{1}, int64(0)) + seqs = []int64{1} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // new tx with another signer and correct sequences msg = newTestMsg(addr1, addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{2, 0}, int64(0)) + privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{2, 0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) // replay fails @@ -147,16 +173,18 @@ func TestAnteHandlerSequences(t *testing.T) { // tx from just second signer with incorrect sequence fails msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{0}, int64(0)) + privs, seqs = []crypto.PrivKey{priv2}, []int64{0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidSequence) // fix the sequence and it passes - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, int64(0)) + tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}, fee) checkValidTx(t, anteHandler, ctx, tx) // another tx from both of them that passes msg = newTestMsg(addr1, addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1, priv2}, []int64{3, 2}, int64(0)) + privs, seqs = []crypto.PrivKey{priv1, priv2}, []int64{3, 2} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) } @@ -199,35 +227,55 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { // set the accounts acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) mapper.SetAccount(ctx, acc1) acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) mapper.SetAccount(ctx, acc2) var tx sdk.Tx + msg := newTestMsg(addr1) + fee := newStdFee() // test good tx and signBytes - msg := newTestMsg(addr1) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) - // test invalid chain_id - tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes("", []int64{1}, msg)) - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) - // test wrong seqs - tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes(ctx.ChainID(), []int64{2}, msg)) - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) - // test wrong msg - tx = newTestTxWithSignBytes(msg, []crypto.PrivKey{priv1}, []int64{1}, sdk.StdSignBytes(ctx.ChainID(), []int64{1}, newTestMsg(addr2))) - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) + chainID := ctx.ChainID() + codeUnauth := sdk.CodeUnauthorized + + cases := []struct { + chainID string + seqs []int64 + fee sdk.StdFee + msg sdk.Msg + code sdk.CodeType + }{ + {"", []int64{1}, fee, msg, codeUnauth}, // test invalid chain_id + {chainID, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs + {chainID, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg + } + + privs, seqs = []crypto.PrivKey{priv1}, []int64{1} + for _, cs := range cases { + tx := newTestTxWithSignBytes( + msg, privs, seqs, fee, + sdk.StdSignBytes(cs.chainID, cs.seqs, cs.fee, cs.msg), + ) + checkInvalidTx(t, anteHandler, ctx, tx, cs.code) + } // test wrong signer if public key exist - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv2}, []int64{1}) + privs, seqs = []crypto.PrivKey{priv2}, []int64{1} + tx = newTestTx(ctx, msg, privs, seqs, fee) checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeUnauthorized) // test wrong signer if public doesn't exist msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + privs, seqs = []crypto.PrivKey{priv1}, []int64{0} + tx = newTestTx(ctx, msg, privs, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) } @@ -244,33 +292,37 @@ func TestAnteHandlerSetPubKey(t *testing.T) { // set the accounts acc1 := mapper.NewAccountWithAddress(ctx, addr1) + acc1.SetCoins(newCoins()) mapper.SetAccount(ctx, acc1) acc2 := mapper.NewAccountWithAddress(ctx, addr2) + acc2.SetCoins(newCoins()) mapper.SetAccount(ctx, acc2) var tx sdk.Tx // test good tx and set public key msg := newTestMsg(addr1) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + fee := newStdFee() + tx = newTestTx(ctx, msg, privs, seqs, fee) checkValidTx(t, anteHandler, ctx, tx) acc1 = mapper.GetAccount(ctx, addr1) - reflect.DeepEqual(acc1.GetPubKey(), priv1.PubKey()) + require.Equal(t, acc1.GetPubKey(), priv1.PubKey()) // test public key not found msg = newTestMsg(addr2) - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) + tx = newTestTx(ctx, msg, privs, seqs, fee) sigs := tx.GetSignatures() sigs[0].PubKey = crypto.PubKey{} - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) acc2 = mapper.GetAccount(ctx, addr2) assert.True(t, acc2.GetPubKey().Empty()) // test invalid signature and public key - tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}) - checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInternal) + tx = newTestTx(ctx, msg, privs, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInvalidPubKey) acc2 = mapper.GetAccount(ctx, addr2) assert.True(t, acc2.GetPubKey().Empty()) diff --git a/x/bank/mapper.go b/x/bank/mapper.go index 76e7f4e2f5..ab2e854202 100644 --- a/x/bank/mapper.go +++ b/x/bank/mapper.go @@ -20,7 +20,7 @@ func NewCoinKeeper(am sdk.AccountMapper) CoinKeeper { func (ck CoinKeeper) SubtractCoins(ctx sdk.Context, addr sdk.Address, amt sdk.Coins) (sdk.Coins, sdk.Error) { acc := ck.am.GetAccount(ctx, addr) if acc == nil { - return amt, sdk.ErrUnrecognizedAddress(addr) + return amt, sdk.ErrUnrecognizedAddress(addr.String()) } coins := acc.GetCoins() From dd4a86b856b30c1477398983f767e765fbed2a72 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 21:20:24 +0100 Subject: [PATCH 13/64] x/auth: crank the test coverage --- examples/basecoin/app/app_test.go | 3 - types/coin.go | 8 ++- x/auth/ante.go | 10 +++- x/auth/ante_test.go | 52 +++++++++------- x/auth/baseaccount_test.go | 98 +++++++++++++++++++++++++------ x/auth/context.go | 6 +- 6 files changed, 131 insertions(+), 46 deletions(-) diff --git a/examples/basecoin/app/app_test.go b/examples/basecoin/app/app_test.go index 4c578579ba..66de2b68b4 100644 --- a/examples/basecoin/app/app_test.go +++ b/examples/basecoin/app/app_test.go @@ -273,9 +273,6 @@ func TestQuizMsg(t *testing.T) { func SignCheckDeliver(t *testing.T, bapp *BasecoinApp, msg sdk.Msg, seq int64, expPass bool) { - // TODO: - var fee sdk.StdFee - // Sign the tx tx := sdk.NewStdTx(msg, fee, []sdk.StdSignature{{ PubKey: priv1.PubKey(), diff --git a/types/coin.go b/types/coin.go index ad541b99bd..92871cd179 100644 --- a/types/coin.go +++ b/types/coin.go @@ -139,8 +139,14 @@ func (coins Coins) IsGTE(coinsB Coins) bool { } // IsZero returns true if there are no coins +// or all coins are zero. func (coins Coins) IsZero() bool { - return len(coins) == 0 + for _, coin := range coins { + if !coin.IsZero() { + return false + } + } + return true } // IsEqual returns true if the two sets of Coins have the same value diff --git a/x/auth/ante.go b/x/auth/ante.go index 4305929b97..2da0d92868 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -64,9 +64,12 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { // first sig pays the fees if i == 0 { - signerAcc, res = deductFees(signerAcc, fee) - if !res.IsOK() { - return ctx, res, true + // TODO: min fee + if !fee.Amount.IsZero() { + signerAcc, res = deductFees(signerAcc, fee) + if !res.IsOK() { + return ctx, res, true + } } } @@ -134,6 +137,7 @@ func processSig( func deductFees(acc sdk.Account, fee sdk.StdFee) (sdk.Account, sdk.Result) { coins := acc.GetCoins() feeAmount := fee.Amount + newCoins := coins.Minus(feeAmount) if !newCoins.IsNotNegative() { errMsg := fmt.Sprintf("%s < %s", coins, feeAmount) diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index cecc4a1418..ecd2226230 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -190,28 +190,38 @@ func TestAnteHandlerSequences(t *testing.T) { // Test logic around fee deduction. func TestAnteHandlerFees(t *testing.T) { - // // setup - // ms, capKey := setupMultiStore() - // mapper := NewAccountMapper(capKey, &BaseAccount{}) - // anteHandler := NewAnteHandler(mapper) - // ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) - // - // // keys and addresses - // priv1, addr1 := privAndAddr() - // priv2, addr2 := privAndAddr() - // - // // set the accounts - // acc1 := mapper.NewAccountWithAddress(ctx, addr1) - // mapper.SetAccount(ctx, acc1) - // acc2 := mapper.NewAccountWithAddress(ctx, addr2) - // mapper.SetAccount(ctx, acc2) - // - // // msg and signatures - // var tx sdk.Tx - // msg := newTestMsg(addr1) - // tx = newTestTx(ctx, msg, []crypto.PrivKey{priv1}, []int64{0}, int64(1)) + // setup + ms, capKey := setupMultiStore() + mapper := NewAccountMapper(capKey, &BaseAccount{}) + anteHandler := NewAnteHandler(mapper) + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) - // TODO + // keys and addresses + priv1, addr1 := privAndAddr() + + // set the accounts + acc1 := mapper.NewAccountWithAddress(ctx, addr1) + mapper.SetAccount(ctx, acc1) + + // msg and signatures + var tx sdk.Tx + msg := newTestMsg(addr1) + privs, seqs := []crypto.PrivKey{priv1}, []int64{0} + fee := sdk.NewStdFee(100, + sdk.Coin{"atom", 150}, + ) + + // signer does not have enough funds to pay the fee + tx = newTestTx(ctx, msg, privs, seqs, fee) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) + + acc1.SetCoins(sdk.Coins{{"atom", 149}}) + mapper.SetAccount(ctx, acc1) + checkInvalidTx(t, anteHandler, ctx, tx, sdk.CodeInsufficientFunds) + + acc1.SetCoins(sdk.Coins{{"atom", 150}}) + mapper.SetAccount(ctx, acc1) + checkValidTx(t, anteHandler, ctx, tx) } func TestAnteHandlerBadSignBytes(t *testing.T) { diff --git a/x/auth/baseaccount_test.go b/x/auth/baseaccount_test.go index 85674af18b..85adc8cdca 100644 --- a/x/auth/baseaccount_test.go +++ b/x/auth/baseaccount_test.go @@ -11,42 +11,106 @@ import ( wire "github.com/cosmos/cosmos-sdk/wire" ) -func TestBaseAccount(t *testing.T) { +func keyPubAddr() (crypto.PrivKey, crypto.PubKey, sdk.Address) { key := crypto.GenPrivKeyEd25519() pub := key.PubKey() addr := pub.Address() + return key.Wrap(), pub, addr +} + +func TestBaseAccountAddressPubKey(t *testing.T) { + _, pub1, addr1 := keyPubAddr() + _, pub2, addr2 := keyPubAddr() + acc := NewBaseAccountWithAddress(addr1) + + // check the address (set) and pubkey (not set) + assert.EqualValues(t, addr1, acc.GetAddress()) + assert.EqualValues(t, crypto.PubKey{}, acc.GetPubKey()) + + // cant override address + err := acc.SetAddress(addr2) + assert.NotNil(t, err) + assert.EqualValues(t, addr1, acc.GetAddress()) + + // set the pubkey + err = acc.SetPubKey(pub1) + assert.Nil(t, err) + assert.Equal(t, pub1, acc.GetPubKey()) + + // cant override pubkey + err = acc.SetPubKey(pub2) + assert.NotNil(t, err) + assert.Equal(t, pub1, acc.GetPubKey()) + + //------------------------------------ + + // can set address on empty account + acc2 := BaseAccount{} + err = acc2.SetAddress(addr2) + assert.Nil(t, err) + assert.EqualValues(t, addr2, acc2.GetAddress()) +} + +func TestBaseAccountCoins(t *testing.T) { + _, _, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + someCoins := sdk.Coins{{"atom", 123}, {"eth", 246}} + + err := acc.SetCoins(someCoins) + assert.Nil(t, err) + assert.Equal(t, someCoins, acc.GetCoins()) +} + +func TestBaseAccountSequence(t *testing.T) { + _, _, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + seq := int64(7) + + err := acc.SetSequence(seq) + assert.Nil(t, err) + assert.Equal(t, seq, acc.GetSequence()) +} + +func TestBaseAccountMarshal(t *testing.T) { + _, pub, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + someCoins := sdk.Coins{{"atom", 123}, {"eth", 246}} seq := int64(7) - acc := NewBaseAccountWithAddress(addr) + // set everything on the account + err := acc.SetPubKey(pub) + assert.Nil(t, err) + err = acc.SetSequence(seq) + assert.Nil(t, err) + err = acc.SetCoins(someCoins) + assert.Nil(t, err) // need a codec for marshaling codec := wire.NewCodec() wire.RegisterCrypto(codec) - err := acc.SetPubKey(pub) - assert.Nil(t, err) - assert.Equal(t, pub, acc.GetPubKey()) - - assert.EqualValues(t, addr, acc.GetAddress()) - - err = acc.SetCoins(someCoins) - assert.Nil(t, err) - assert.Equal(t, someCoins, acc.GetCoins()) - - err = acc.SetSequence(seq) - assert.Nil(t, err) - assert.Equal(t, seq, acc.GetSequence()) - b, err := codec.MarshalBinary(acc) assert.Nil(t, err) - var acc2 BaseAccount + acc2 := BaseAccount{} err = codec.UnmarshalBinary(b, &acc2) assert.Nil(t, err) assert.Equal(t, acc, acc2) + // error on bad bytes acc2 = BaseAccount{} err = codec.UnmarshalBinary(b[:len(b)/2], &acc2) assert.NotNil(t, err) + +} + +func TestBaseAccountGetSet(t *testing.T) { + _, _, addr := keyPubAddr() + acc := NewBaseAccountWithAddress(addr) + + assert.Panics(t, func() { acc.Get("key") }) + assert.Panics(t, func() { acc.Set("key", "value") }) } diff --git a/x/auth/context.go b/x/auth/context.go index 90de99f783..91259d9e6d 100644 --- a/x/auth/context.go +++ b/x/auth/context.go @@ -38,5 +38,9 @@ func WithSigners(ctx types.Context, accounts []types.Account) types.Context { } func GetSigners(ctx types.Context) []types.Account { - return ctx.Value(contextKeySigners).([]types.Account) + v := ctx.Value(contextKeySigners) + if v == nil { + return []types.Account{} + } + return v.([]types.Account) } From 2ed4de5e8dc458b7caecc6840520d56eac807e25 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 21:40:44 +0100 Subject: [PATCH 14/64] shame: forgot x/auth/context_test.go --- x/auth/context_test.go | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 x/auth/context_test.go diff --git a/x/auth/context_test.go b/x/auth/context_test.go new file mode 100644 index 0000000000..0e4db8b080 --- /dev/null +++ b/x/auth/context_test.go @@ -0,0 +1,39 @@ +package auth + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + abci "github.com/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func TestContextWithSigners(t *testing.T) { + ms, _ := setupMultiStore() + ctx := sdk.NewContext(ms, abci.Header{ChainID: "mychainid"}, false, nil) + + _, _, addr1 := keyPubAddr() + _, _, addr2 := keyPubAddr() + acc1 := NewBaseAccountWithAddress(addr1) + acc1.SetSequence(7132) + acc2 := NewBaseAccountWithAddress(addr2) + acc2.SetSequence(8821) + + // new ctx has no signers + signers := GetSigners(ctx) + assert.Equal(t, 0, len(signers)) + + ctx2 := WithSigners(ctx, []sdk.Account{&acc1, &acc2}) + + // original context is unchanged + signers = GetSigners(ctx) + assert.Equal(t, 0, len(signers)) + + // new context has signers + signers = GetSigners(ctx2) + assert.Equal(t, 2, len(signers)) + assert.Equal(t, acc1, *(signers[0].(*BaseAccount))) + assert.Equal(t, acc2, *(signers[1].(*BaseAccount))) +} From a908a01fe2fe8300a43a831787fc9fe9c6fbc768 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 21:54:21 +0100 Subject: [PATCH 15/64] types/tx_msg_test.go --- types/tx_msg_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 types/tx_msg_test.go diff --git a/types/tx_msg_test.go b/types/tx_msg_test.go new file mode 100644 index 0000000000..f72cdea26e --- /dev/null +++ b/types/tx_msg_test.go @@ -0,0 +1,30 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + crypto "github.com/tendermint/go-crypto" +) + +func newStdFee() StdFee { + return NewStdFee(100, + Coin{"atom", 150}, + ) +} + +func TestStdTx(t *testing.T) { + priv := crypto.GenPrivKeyEd25519() + addr := priv.PubKey().Address() + msg := NewTestMsg(addr) + fee := newStdFee() + sigs := []StdSignature{} + + tx := NewStdTx(msg, fee, sigs) + assert.Equal(t, msg, tx.GetMsg()) + assert.Equal(t, sigs, tx.GetSignatures()) + + feePayer := FeePayer(tx) + assert.Equal(t, addr, feePayer) +} From 7c3213fa00198e75fa732920807b86eee86a591c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 21:53:27 +0100 Subject: [PATCH 16/64] fixes from review --- types/errors.go | 2 +- types/tx_msg.go | 29 +++++++++++++++++++++++++++ x/auth/ante.go | 4 +++- x/auth/ante_test.go | 40 +++++++++++++------------------------- x/auth/baseaccount_test.go | 5 +++-- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/types/errors.go b/types/errors.go index 5c96d8c241..48e08b1164 100644 --- a/types/errors.go +++ b/types/errors.go @@ -52,7 +52,7 @@ func CodeToDefaultMsg(code CodeType) string { case CodeUnrecognizedAddress: return "Unrecognized address" case CodeInvalidPubKey: - return "Missing pubkey" + return "Invalid pubkey" default: return fmt.Sprintf("Unknown code %d", code) } diff --git a/types/tx_msg.go b/types/tx_msg.go index b41d9879a9..21bc330540 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -146,3 +146,32 @@ func (msg StdSignMsg) Bytes() []byte { // Application function variable used to unmarshal transaction bytes type TxDecoder func(txBytes []byte) (Tx, Error) + +//__________________________________________________________ + +var _ Msg = (*TestMsg)(nil) + +// msg type for testing +type TestMsg struct { + signers []Address +} + +func NewTestMsg(addrs ...Address) *TestMsg { + return &TestMsg{ + signers: addrs, + } +} + +func (msg *TestMsg) Type() string { return "TestMsg" } +func (msg *TestMsg) Get(key interface{}) (value interface{}) { return nil } +func (msg *TestMsg) GetSignBytes() []byte { + bz, err := json.Marshal(msg.signers) + if err != nil { + panic(err) + } + return bz +} +func (msg *TestMsg) ValidateBasic() Error { return nil } +func (msg *TestMsg) GetSigners() []Address { + return msg.signers +} diff --git a/x/auth/ante.go b/x/auth/ante.go index 2da0d92868..43b9bb8341 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -133,7 +133,9 @@ func processSig( return } -// deduct the fee from the account +// Deduct the fee from the account. +// We could use the CoinKeeper (in addition to the AccountMapper, +// because the CoinKeeper doesn't give us accounts), but it seems easier to do this. func deductFees(acc sdk.Account, fee sdk.StdFee) (sdk.Account, sdk.Result) { coins := acc.GetCoins() feeAmount := fee.Amount diff --git a/x/auth/ante_test.go b/x/auth/ante_test.go index ecd2226230..5bbb3d0b69 100644 --- a/x/auth/ante_test.go +++ b/x/auth/ante_test.go @@ -1,7 +1,6 @@ package auth import ( - "encoding/json" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,29 +10,8 @@ import ( crypto "github.com/tendermint/go-crypto" ) -// msg type for testing -type testMsg struct { - signers []sdk.Address -} - -func newTestMsg(addrs ...sdk.Address) *testMsg { - return &testMsg{ - signers: addrs, - } -} - -func (msg *testMsg) Type() string { return "testMsg" } -func (msg *testMsg) Get(key interface{}) (value interface{}) { return nil } -func (msg *testMsg) GetSignBytes() []byte { - bz, err := json.Marshal(msg.signers) - if err != nil { - panic(err) - } - return bz -} -func (msg *testMsg) ValidateBasic() sdk.Error { return nil } -func (msg *testMsg) GetSigners() []sdk.Address { - return msg.signers +func newTestMsg(addrs ...sdk.Address) *sdk.TestMsg { + return sdk.NewTestMsg(addrs...) } func newStdFee() sdk.StdFee { @@ -246,6 +224,10 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { var tx sdk.Tx msg := newTestMsg(addr1) fee := newStdFee() + fee2 := newStdFee() + fee2.Gas += 100 + fee3 := newStdFee() + fee3.Amount[0].Amount += 100 // test good tx and signBytes privs, seqs := []crypto.PrivKey{priv1}, []int64{0} @@ -253,6 +235,7 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { checkValidTx(t, anteHandler, ctx, tx) chainID := ctx.ChainID() + chainID2 := chainID + "somemorestuff" codeUnauth := sdk.CodeUnauthorized cases := []struct { @@ -262,9 +245,12 @@ func TestAnteHandlerBadSignBytes(t *testing.T) { msg sdk.Msg code sdk.CodeType }{ - {"", []int64{1}, fee, msg, codeUnauth}, // test invalid chain_id - {chainID, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs - {chainID, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg + {chainID2, []int64{1}, fee, msg, codeUnauth}, // test wrong chain_id + {chainID, []int64{2}, fee, msg, codeUnauth}, // test wrong seqs + {chainID, []int64{1, 2}, fee, msg, codeUnauth}, // test wrong seqs + {chainID, []int64{1}, fee, newTestMsg(addr2), codeUnauth}, // test wrong msg + {chainID, []int64{1}, fee2, newTestMsg(addr2), codeUnauth}, // test wrong fee + {chainID, []int64{1}, fee3, newTestMsg(addr2), codeUnauth}, // test wrong fee } privs, seqs = []crypto.PrivKey{priv1}, []int64{1} diff --git a/x/auth/baseaccount_test.go b/x/auth/baseaccount_test.go index 85adc8cdca..b2f5b54ae2 100644 --- a/x/auth/baseaccount_test.go +++ b/x/auth/baseaccount_test.go @@ -27,7 +27,7 @@ func TestBaseAccountAddressPubKey(t *testing.T) { assert.EqualValues(t, addr1, acc.GetAddress()) assert.EqualValues(t, crypto.PubKey{}, acc.GetPubKey()) - // cant override address + // can't override address err := acc.SetAddress(addr2) assert.NotNil(t, err) assert.EqualValues(t, addr1, acc.GetAddress()) @@ -37,7 +37,7 @@ func TestBaseAccountAddressPubKey(t *testing.T) { assert.Nil(t, err) assert.Equal(t, pub1, acc.GetPubKey()) - // cant override pubkey + // can't override pubkey err = acc.SetPubKey(pub2) assert.NotNil(t, err) assert.Equal(t, pub1, acc.GetPubKey()) @@ -111,6 +111,7 @@ func TestBaseAccountGetSet(t *testing.T) { _, _, addr := keyPubAddr() acc := NewBaseAccountWithAddress(addr) + // Get/Set are not yet defined - all values cause a panic. assert.Panics(t, func() { acc.Get("key") }) assert.Panics(t, func() { acc.Set("key", "value") }) } From ad705fdea10f56ceb71fea7570ebac7e7bb8d2a1 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 1 Mar 2018 02:16:54 -0500 Subject: [PATCH 17/64] bump version added rest server and status endpoint added get block endpoint added latest block endpoint add 404 if height is out of bounds add version endpoint add validators endpoint export GetBlockHeight add keys endpoints add txs endpoints added verb limiters to ednpoints only output node info + json structure improvement fixed wrong body parsing github PR template crypto.Address -> sdk.Address revert to old go-wire update glide remove print statement and update glide fix #554 add .DS_Store to .gitignore Massive consolidation: queue, data storage struct, store, logic, ... Small fixes --- .vscode/launch.json | 21 + client/keys/add.go | 76 +++ client/keys/delete.go | 42 ++ client/keys/list.go | 42 +- client/keys/show.go | 48 +- client/keys/update.go | 43 ++ client/keys/utils.go | 8 + client/lcd/root.go | 51 +- client/rpc/block.go | 107 ++++- client/rpc/status.go | 39 +- client/rpc/validators.go | 83 +++- client/tx/search.go | 51 +- client/tx/tx.go | 147 +++++- docs/spec/governance/governance.md | 659 ++++++++++++++++++++++++++ examples/basecoin/cmd/basecli/main.go | 2 +- glide.lock | 263 ++++++++++ glide.yaml | 54 +++ version/command.go | 19 +- 18 files changed, 1662 insertions(+), 93 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 docs/spec/governance/governance.md create mode 100644 glide.lock create mode 100644 glide.yaml diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..76977265a3 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "go", + "request": "launch", + "mode": "debug", + "remotePath": "", + "port": 2345, + "host": "127.0.0.1", + "program": "${fileDirname}", + "env": {}, + "args": [], + "showLog": true + } + ] +} \ No newline at end of file diff --git a/client/keys/add.go b/client/keys/add.go index 7472dce27f..cfaf526460 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -1,9 +1,12 @@ package keys import ( + "encoding/json" "fmt" + "net/http" "github.com/cosmos/cosmos-sdk/client" + "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -120,3 +123,76 @@ func printCreate(info keys.Info, seed string) { panic(fmt.Sprintf("I can't speak: %s", output)) } } + +// REST + +type NewKeyBody struct { + Name string `json:"name"` + Password string `json:"password"` + // TODO make seed mandatory + // Seed string `json="seed"` + Type string `json:"type"` +} + +func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { + var kb keys.Keybase + var m NewKeyBody + + kb, err := GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + decoder := json.NewDecoder(r.Body) + err = decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + if m.Name == "" { + w.WriteHeader(400) + w.Write([]byte("You have to specify a name for the locally stored account.")) + return + } + + // algo type defaults to ed25519 + if m.Type == "" { + m.Type = "ed25519" + } + algo := keys.CryptoAlgo(m.Type) + + _, _, err = kb.Create(m.Name, m.Password, algo) + // TODO handle different errors + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.WriteHeader(200) +} + +// function to just a new seed to display in the UI before actually persisting it in the keybase +func getSeed(algo keys.CryptoAlgo) string { + kb := client.MockKeyBase() + pass := "throwing-this-key-away" + name := "inmemorykey" + + _, seed, _ := kb.Create(name, pass, algo) + return seed +} + +func SeedRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + algoType := vars["type"] + // algo type defaults to ed25519 + if algoType == "" { + algoType = "ed25519" + } + algo := keys.CryptoAlgo(algoType) + + seed := getSeed(algo) + w.Write([]byte(seed)) +} diff --git a/client/keys/delete.go b/client/keys/delete.go index 65a9513272..b0327771b1 100644 --- a/client/keys/delete.go +++ b/client/keys/delete.go @@ -1,10 +1,14 @@ package keys import ( + "encoding/json" "fmt" + "net/http" "github.com/cosmos/cosmos-sdk/client" + "github.com/gorilla/mux" "github.com/pkg/errors" + keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) @@ -43,3 +47,41 @@ func runDeleteCmd(cmd *cobra.Command, args []string) error { fmt.Println("Password deleted forever (uh oh!)") return nil } + +// REST + +type DeleteKeyBody struct { + Password string `json:"password"` +} + +func DeleteKeyRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["name"] + var kb keys.Keybase + var m DeleteKeyBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + kb, err = GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + // TODO handle error if key is not available or pass is wrong + err = kb.Delete(name, m.Password) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(200) +} diff --git a/client/keys/list.go b/client/keys/list.go index 14c4408781..97ccf8a90c 100644 --- a/client/keys/list.go +++ b/client/keys/list.go @@ -1,6 +1,13 @@ package keys -import "github.com/spf13/cobra" +import ( + "encoding/json" + "net/http" + + "github.com/spf13/cobra" +) + +// CMD // listKeysCmd represents the list command var listKeysCmd = &cobra.Command{ @@ -23,3 +30,36 @@ func runListCmd(cmd *cobra.Command, args []string) error { } return err } + +//REST + +func QueryKeysRequestHandler(w http.ResponseWriter, r *http.Request) { + kb, err := GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + infos, err := kb.List() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + // an empty list will be JSONized as null, but we want to keep the empty list + if len(infos) == 0 { + w.Write([]byte("[]")) + return + } + keysOutput := make([]KeyOutput, len(infos)) + for i, info := range infos { + keysOutput[i] = KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()} + } + output, err := json.MarshalIndent(keysOutput, "", " ") + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} diff --git a/client/keys/show.go b/client/keys/show.go index a22cb4bc88..bb60b5bc0b 100644 --- a/client/keys/show.go +++ b/client/keys/show.go @@ -1,7 +1,12 @@ package keys import ( + "encoding/json" + "net/http" + + "github.com/gorilla/mux" "github.com/pkg/errors" + keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) @@ -13,20 +18,51 @@ var showKeysCmd = &cobra.Command{ RunE: runShowCmd, } +func getKey(name string) (keys.Info, error) { + kb, err := GetKeyBase() + if err != nil { + return keys.Info{}, err + } + + return kb.Get(name) +} + +// CMD + 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) + info, err := getKey(name) if err == nil { printInfo(info) } return err } + +// REST + +func GetKeyRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["name"] + + info, err := getKey(name) + // TODO check for the error if key actually does not exist, instead of assuming this as the reason + if err != nil { + w.WriteHeader(404) + w.Write([]byte(err.Error())) + return + } + + keyOutput := KeyOutput{Name: info.Name, Address: info.PubKey.Address().String()} + output, err := json.MarshalIndent(keyOutput, "", " ") + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) +} diff --git a/client/keys/update.go b/client/keys/update.go index 0e0f881c6e..6bb4d1ac0f 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -1,10 +1,14 @@ package keys import ( + "encoding/json" "fmt" + "net/http" "github.com/cosmos/cosmos-sdk/client" + "github.com/gorilla/mux" "github.com/pkg/errors" + keys "github.com/tendermint/go-crypto/keys" "github.com/spf13/cobra" ) @@ -48,3 +52,42 @@ func runUpdateCmd(cmd *cobra.Command, args []string) error { fmt.Println("Password successfully updated!") return nil } + +// REST + +type UpdateKeyBody struct { + NewPassword string `json:"new_password"` + OldPassword string `json:"old_password"` +} + +func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + name := vars["name"] + var kb keys.Keybase + var m UpdateKeyBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + kb, err = GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + // TODO check if account exists and if password is correct + err = kb.Update(name, m.OldPassword, m.NewPassword) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(200) +} diff --git a/client/keys/utils.go b/client/keys/utils.go index 19e63d7482..b6a83ec7c1 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -20,6 +20,14 @@ var ( keybase keys.Keybase ) +// used for outputting keys.Info over REST +type KeyOutput struct { + Name string `json:"name"` + Address string `json:"address"` + // TODO add pubkey? + // Pubkey string `json:"pubkey"` +} + // GetKeyBase initializes a keybase based on the configuration func GetKeyBase() (keys.Keybase, error) { if keybase == nil { diff --git a/client/lcd/root.go b/client/lcd/root.go index 9187af9a1a..cf72087b7d 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -1,11 +1,18 @@ package lcd import ( - "errors" + "net/http" + "github.com/gorilla/mux" "github.com/spf13/cobra" + "github.com/spf13/viper" + wire "github.com/tendermint/go-wire" - "github.com/cosmos/cosmos-sdk/client" + client "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + rpc "github.com/cosmos/cosmos-sdk/client/rpc" + tx "github.com/cosmos/cosmos-sdk/client/tx" + version "github.com/cosmos/cosmos-sdk/version" ) const ( @@ -13,19 +20,14 @@ const ( 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 { +func ServeCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ - Use: "serve", + Use: "rest-server", Short: "Start LCD (light-client daemon), a local REST server", - RunE: todoNotImplemented, + RunE: startRESTServer(cdc), } // TODO: handle unix sockets also? cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to") @@ -34,3 +36,32 @@ func ServeCommand() *cobra.Command { cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") return cmd } + +func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { + return func(cmd *cobra.Command, args []string) error { + bind := viper.GetString(flagBind) + r := initRouter(cdc) + return http.ListenAndServe(bind, r) + } +} + +func initRouter(cdc *wire.Codec) http.Handler { + r := mux.NewRouter() + r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") + r.HandleFunc("/node_info", rpc.NodeStatusRequestHandler).Methods("GET") + r.HandleFunc("/keys", keys.QueryKeysRequestHandler).Methods("GET") + r.HandleFunc("/keys", keys.AddNewKeyRequestHandler).Methods("POST") + r.HandleFunc("/keys/seed", keys.SeedRequestHandler).Methods("GET") + r.HandleFunc("/keys/{name}", keys.GetKeyRequestHandler).Methods("GET") + r.HandleFunc("/keys/{name}", keys.UpdateKeyRequestHandler).Methods("PUT") + r.HandleFunc("/keys/{name}", keys.DeleteKeyRequestHandler).Methods("DELETE") + r.HandleFunc("/txs", tx.SearchTxRequestHandler(cdc)).Methods("GET") + r.HandleFunc("/txs/{hash}", tx.QueryTxRequestHandler(cdc)).Methods("GET") + r.HandleFunc("/txs/sign", tx.SignTxRequstHandler).Methods("POST") + r.HandleFunc("/txs/broadcast", tx.BroadcastTxRequestHandler).Methods("POST") + r.HandleFunc("/blocks/latest", rpc.LatestBlockRequestHandler).Methods("GET") + r.HandleFunc("/blocks/{height}", rpc.BlockRequestHandler).Methods("GET") + r.HandleFunc("/validatorsets/latest", rpc.LatestValidatorsetRequestHandler).Methods("GET") + r.HandleFunc("/validatorsets/{height}", rpc.ValidatorsetRequestHandler).Methods("GET") + return r +} diff --git a/client/rpc/block.go b/client/rpc/block.go index 9519187891..7f197051a9 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -1,11 +1,13 @@ package rpc import ( + "encoding/json" "fmt" + "net/http" "strconv" + "github.com/gorilla/mux" "github.com/spf13/cobra" - wire "github.com/tendermint/go-wire" "github.com/cosmos/cosmos-sdk/client" ) @@ -18,7 +20,7 @@ func blockCommand() *cobra.Command { cmd := &cobra.Command{ Use: "block [height]", Short: "Get verified data for a the block at given height", - RunE: getBlock, + RunE: printBlock, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") // TODO: change this to false when we can @@ -27,7 +29,47 @@ func blockCommand() *cobra.Command { return cmd } -func getBlock(cmd *cobra.Command, args []string) error { +func getBlock(height *int64) ([]byte, error) { + // get the node + node, err := client.GetNode() + if err != nil { + return nil, err + } + + // TODO: actually honor the --select flag! + // header -> BlockchainInfo + // header, tx -> Block + // results -> BlockResults + res, err := node.Block(height) + if err != nil { + return nil, err + } + + // TODO move maarshalling into cmd/rest functions + // output, err := tmwire.MarshalJSON(res) + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + return nil, err + } + return output, nil +} + +func GetChainHeight() (int64, error) { + node, err := client.GetNode() + if err != nil { + return -1, err + } + status, err := node.Status() + if err != nil { + return -1, err + } + height := status.LatestBlockHeight + return height, nil +} + +// CMD + +func printBlock(cmd *cobra.Command, args []string) error { var height *int64 // optional height if len(args) > 0 { @@ -41,26 +83,51 @@ func getBlock(cmd *cobra.Command, args []string) error { } } - // 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 := wire.MarshalJSON(res) - // output, err := json.MarshalIndent(res, " ", "") + output, err := getBlock(height) if err != nil { return err } fmt.Println(string(output)) return nil } + +// REST + +func BlockRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + height, err := strconv.ParseInt(vars["height"], 10, 64) + if err != nil { + w.WriteHeader(400) + w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'.")) + return + } + chainHeight, err := GetChainHeight() + if height > chainHeight { + w.WriteHeader(404) + w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + return + } + output, err := getBlock(&height) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} + +func LatestBlockRequestHandler(w http.ResponseWriter, r *http.Request) { + height, err := GetChainHeight() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + output, err := getBlock(&height) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} diff --git a/client/rpc/status.go b/client/rpc/status.go index 4ca6e9084c..54926bc3ed 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -1,31 +1,40 @@ package rpc import ( + "encoding/json" "fmt" + "net/http" "github.com/spf13/cobra" wire "github.com/tendermint/go-wire" "github.com/cosmos/cosmos-sdk/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" ) func statusCommand() *cobra.Command { cmd := &cobra.Command{ Use: "status", Short: "Query remote node for status", - RunE: checkStatus, + RunE: printNodeStatus, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") return cmd } -func checkStatus(cmd *cobra.Command, args []string) error { +func getNodeStatus() (*ctypes.ResultStatus, error) { // get the node node, err := client.GetNode() if err != nil { - return err + return &ctypes.ResultStatus{}, err } - res, err := node.Status() + return node.Status() +} + +// CMD + +func printNodeStatus(cmd *cobra.Command, args []string) error { + status, err := getNodeStatus() if err != nil { return err } @@ -35,6 +44,28 @@ func checkStatus(cmd *cobra.Command, args []string) error { if err != nil { return err } + fmt.Println(string(output)) return nil } + +// REST + +// TODO match desired spec output +func NodeStatusRequestHandler(w http.ResponseWriter, r *http.Request) { + status, err := getNodeStatus() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + nodeInfo := status.NodeInfo + output, err := json.MarshalIndent(nodeInfo, "", " ") + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 94ec4226e1..32c7680ec6 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -1,11 +1,13 @@ package rpc import ( + "encoding/json" "fmt" + "net/http" "strconv" + "github.com/gorilla/mux" "github.com/spf13/cobra" - wire "github.com/tendermint/go-wire" "github.com/cosmos/cosmos-sdk/client" ) @@ -14,7 +16,7 @@ func validatorCommand() *cobra.Command { cmd := &cobra.Command{ Use: "validatorset ", Short: "Get the full validator set at given height", - RunE: getValidators, + RunE: printValidators, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") // TODO: change this to false when we can @@ -22,7 +24,28 @@ func validatorCommand() *cobra.Command { return cmd } -func getValidators(cmd *cobra.Command, args []string) error { +func getValidators(height *int64) ([]byte, error) { + // get the node + node, err := client.GetNode() + if err != nil { + return nil, err + } + + res, err := node.Validators(height) + if err != nil { + return nil, err + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + return nil, err + } + return output, nil +} + +// CMD + +func printValidators(cmd *cobra.Command, args []string) error { var height *int64 // optional height if len(args) > 0 { @@ -36,22 +59,52 @@ func getValidators(cmd *cobra.Command, args []string) error { } } - // get the node - node, err := client.GetNode() + output, err := getValidators(height) if err != nil { return err } - res, err := node.Validators(height) - if err != nil { - return err - } - - output, err := wire.MarshalJSON(res) - // output, err := json.MarshalIndent(res, " ", "") - if err != nil { - return err - } fmt.Println(string(output)) return nil } + +// REST + +func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + height, err := strconv.ParseInt(vars["height"], 10, 64) + if err != nil { + w.WriteHeader(400) + w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'.")) + return + } + chainHeight, err := GetChainHeight() + if height > chainHeight { + w.WriteHeader(404) + w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + return + } + output, err := getValidators(&height) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} + +func LatestValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { + height, err := GetChainHeight() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + output, err := getValidators(&height) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) +} diff --git a/client/tx/search.go b/client/tx/search.go index 8356f99ca6..90f89e3e2c 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -3,6 +3,7 @@ package tx import ( "errors" "fmt" + "net/http" "strings" "github.com/spf13/cobra" @@ -24,7 +25,7 @@ func SearchTxCmd(cmdr commander) *cobra.Command { cmd := &cobra.Command{ Use: "txs", Short: "Search for all transactions that match the given tags", - RunE: cmdr.searchTxCmd, + RunE: cmdr.searchAndPrintTx, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") // TODO: change this to false once proofs built in @@ -34,10 +35,9 @@ func SearchTxCmd(cmdr commander) *cobra.Command { return cmd } -func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error { - tags := viper.GetStringSlice(flagTags) +func (c commander) searchTx(tags []string) ([]byte, error) { if len(tags) == 0 { - return errors.New("Must declare at least one tag to search") + return nil, errors.New("Must declare at least one tag to search") } // XXX: implement ANY query := strings.Join(tags, " AND ") @@ -45,27 +45,25 @@ func (c commander) searchTxCmd(cmd *cobra.Command, args []string) error { // get the node node, err := client.GetNode() if err != nil { - return err + return nil, err } prove := !viper.GetBool(client.FlagTrustNode) res, err := node.TxSearch(query, prove) if err != nil { - return err + return nil, err } info, err := formatTxResults(c.cdc, res) if err != nil { - return err + return nil, err } output, err := c.cdc.MarshalJSON(info) if err != nil { - return err + return nil, err } - fmt.Println(string(output)) - - return nil + return output, nil } func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) { @@ -79,3 +77,34 @@ func formatTxResults(cdc *wire.Codec, res []*ctypes.ResultTx) ([]txInfo, error) } return out, nil } + +// CMD + +func (c commander) searchAndPrintTx(cmd *cobra.Command, args []string) error { + tags := viper.GetStringSlice(flagTags) + + output, err := c.searchTx(tags) + if err != nil { + return err + } + + fmt.Println(string(output)) + return nil +} + +// REST + +func SearchTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { + c := commander{cdc} + return func(w http.ResponseWriter, r *http.Request) { + tag := r.FormValue("tag") + tags := []string{tag} + output, err := c.searchTx(tags) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} diff --git a/client/tx/tx.go b/client/tx/tx.go index 183efef1dc..1f30798708 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -4,12 +4,20 @@ import ( "encoding/hex" "encoding/json" "fmt" + "net/http" + "strconv" + "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" + keybase "github.com/cosmos/cosmos-sdk/client/keys" + sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" + keys "github.com/tendermint/go-crypto/keys" + wire "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" @@ -22,7 +30,7 @@ func QueryTxCmd(cmdr commander) *cobra.Command { cmd := &cobra.Command{ Use: "tx [hash]", Short: "Matches this txhash over all committed blocks", - RunE: cmdr.queryTxCmd, + RunE: cmdr.queryAndPrintTx, } cmd.Flags().StringP(client.FlagNode, "n", "tcp://localhost:46657", "Node to connect to") // TODO: change this to false when we can @@ -30,42 +38,28 @@ func QueryTxCmd(cmdr commander) *cobra.Command { 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) +func (c commander) queryTx(hashHexStr string, trustNode bool) ([]byte, error) { + hash, err := hex.DecodeString(hashHexStr) if err != nil { - return err + return nil, err } // get the node node, err := client.GetNode() if err != nil { - return err + return nil, err } - prove := !viper.GetBool(client.FlagTrustNode) - res, err := node.Tx(hash, prove) + res, err := node.Tx(hash, !trustNode) if err != nil { - return err + return nil, err } info, err := formatTxResult(c.cdc, res) if err != nil { - return err + return nil, err } - output, err := json.MarshalIndent(info, "", " ") - if err != nil { - return err - } - fmt.Println(string(output)) - - return nil + return json.MarshalIndent(info, "", " ") } func formatTxResult(cdc *wire.Codec, res *ctypes.ResultTx) (txInfo, error) { @@ -98,3 +92,110 @@ func parseTx(cdc *wire.Codec, txBytes []byte) (sdk.Tx, error) { } return tx, nil } + +// CMD + +// command to query for a transaction +func (c commander) queryAndPrintTx(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 + hashHexStr := args[0] + trustNode := viper.GetBool(client.FlagTrustNode) + + output, err := c.queryTx(hashHexStr, trustNode) + if err != nil { + return err + } + fmt.Println(string(output)) + + return nil +} + +// REST + +func QueryTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { + c := commander{cdc} + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + hashHexStr := vars["hash"] + trustNode, err := strconv.ParseBool(r.FormValue("trust_node")) + // trustNode defaults to true + if err != nil { + trustNode = true + } + + output, err := c.queryTx(hashHexStr, trustNode) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write(output) + } +} + +// TODO refactor into different files show, sign, broadcast + +type SignTxBody struct { + Name string `json="name"` + Password string `json="password"` + TxBytes string `json="tx"` +} + +func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) { + var kb keys.Keybase + var m SignTxBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + kb, err = keybase.GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + //TODO check if account exists + sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes)) + if err != nil { + w.WriteHeader(403) + w.Write([]byte(err.Error())) + return + } + + w.Write(sig.Bytes()) +} + +type BroadcastTxBody struct { + TxBytes string `json="tx"` +} + +func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) { + var m BroadcastTxBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + res, err := client.BroadcastTx([]byte(m.TxBytes)) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + w.Write([]byte(string(res.Height))) +} diff --git a/docs/spec/governance/governance.md b/docs/spec/governance/governance.md new file mode 100644 index 0000000000..2e40e92dd4 --- /dev/null +++ b/docs/spec/governance/governance.md @@ -0,0 +1,659 @@ +# Governance documentation + +*Disclaimer: This is work in progress. Mechanisms are susceptible to change.* + +This document describes the high-level architecture of the governance module. The governance module allows bonded Atom holders to vote on proposals on a 1 bonded Atom 1 vote basis. + +## Design overview + +The governance process is divided in a few steps that are outlined below: + +- **Proposal submission:** Proposal is submitted to the blockchain with a deposit +- **Vote:** Once deposit reaches a certain value (`MinDeposit`), proposal is confirmed and vote opens. Bonded Atom holders can then send `TxGovVote` transactions to vote on the proposal +- If the proposal involves a software upgrade: + - **Signal:** Validators start signaling that they are ready to switch to the new version + - **Switch:** Once more than 75% of validators have signaled that they are ready to switch, their software automatically flips to the new version + +## Proposal submission + +### Right to submit a proposal + +Any Atom holder, whether bonded or unbonded, can submit proposals by sending a `TxGovProposal` transaction. Once a proposal is submitted, it is identified by its unique `proposalID`. + +### Proposal filter (minimum deposit) + +To prevent spam, proposals must be submitted with a deposit in Atoms. Voting period will not start as long as the proposal's deposit is smaller than the minimum deposit `MinDeposit`. + +When a proposal is submitted, it has to be accompagnied by a deposit that must be strictly positive but can be inferior to `MinDeposit`. Indeed, the submitter need not pay for the entire deposit on its own. If a proposal's deposit is strictly inferior to `MinDeposit`, other Atom holders can increase the proposal's deposit by sending a `TxGovDeposit` transaction. Once the proposals's deposit reaches `MinDeposit`, it enters voting period. + +### Deposit refund + +There are two instances where Atom holders that deposited can claim back their deposit: +- If the proposal is accepted +- If the proposal's deposit does not reach `MinDeposit` for a period longer than `MaxDepositPeriod` (initial value: 2 months). Then the proposal is considered closed and nobody can deposit on it anymore. + +In such instances, Atom holders that deposited can send a `TxGovClaimDeposit` transaction to retrieve their share of the deposit. + +### Proposal types + +In the initial version of the governance module, there are two types of proposal: +- `PlainTextProposal`. All the proposals that do not involve a modification of the source code go under this type. For example, an opinion poll would use a proposal of type `PlainTextProposal` +- `SoftwareUpgradeProposal`. If accepted, validators are expected to update their software in accordance with the proposal. They must do so by following a 2-steps process described in the [Software Upgrade](#software-upgrade) section below. Software upgrade roadmap may be discussed and agreed on via `PlainTextProposals`, but actual software upgrades must be performed via `SoftwareUpgradeProposals`. + +### Proposal categories + +There are two categories of proposal: +- `Regular` +- `Urgent` + +These two categories are strictly identical except that `Urgent` proposals can be accepted faster if a certain condition is met. For more information, see [Threshold](#threshold) section. + +## Vote + +### Participants + +*Participants* are users that have the right to vote on proposals. On the Cosmos Hub, participants are bonded Atom holders. Unbonded Atom holders and other users do not get the right to participate in governance. However, they can submit and deposit on proposals. + +Note that some *participants* can be forbidden to vote on a proposal under a certain validator if: +- *participant* bonded or unbonded Atoms to said validator after proposal entered voting period +- *participant* became validator after proposal entered voting period + +This does not prevent *participant* to vote with Atoms bonded to other validators. For example, if a *participant* bonded some Atoms to validator A before a proposal entered voting period and other Atoms to validator B after proposal entered voting period, only the vote under validator B will be forbidden. + +### Voting period + +Once a proposal reaches `MinDeposit`, it immediately enters `Voting period`. We define `Voting period` as the interval between the moment the vote opens and the moment the vote closes. `Voting period` should always be shorter than `Unbonding period` to prevent double voting. The initial value of `Voting period` is 2 weeks. + +### Option set + +The option set of a proposal refers to the set of choices a participant can choose from when casting its vote. + +The initial option set includes the following options: +- `Yes` +- `No` +- `NoWithVeto` +- `Abstain` + +`NoWithVeto` counts as `No` but also adds a `Veto` vote. `Abstain` option allows voters to signal that they do not intend to vote in favor or against the proposal but accept the result of the vote. + +*Note: from the UI, for urgent proposals we should maybe add a ‘Not Urgent’ option that casts a `NoWithVeto` vote.* + +### Quorum + +Quorum is defined as the minimum percentage of voting power that needs to be casted on a proposal for the result to be valid. + +In the initial version of the governance module, there will be no quorum enforced by the protocol. Participation is ensured via the combination of inheritance and validator's punishment for non-voting. + +### Threshold + +Threshold is defined as the minimum proportion of `Yes` votes (excluding `Abstain` votes) for the proposal to be accepted. + +Initially, the threshold is set at 50% with a possibility to veto if more than 1/3rd of votes (excluding `Abstain` votes) are `NoWithVeto` votes. This means that proposals are accepted if the proportion of `Yes` votes (excluding `Abstain` votes) at the end of the voting period is superior to 50% and if the proportion of `NoWithVeto` votes is inferior to 1/3 (excluding `Abstain` votes). + +`Urgent` proposals also work with the aforementioned threshold, except there is another condition that can accelerate the acceptance of the proposal. Namely, if the ratio of `Yes` votes to `InitTotalVotingPower` exceeds 2:3, `UrgentProposal` will be immediately accepted, even if the `Voting period` is not finished. `InitTotalVotingPower` is the total voting power of all bonded Atom holders at the moment when the vote opens. + +### Inheritance + +If a delegator does not vote, it will inherit its validator vote. + +- If the delegator votes before its validator, it will not inherit from the validator's vote. +- If the delegator votes after its validator, it will override its validator vote with its own. If the proposal is a `Urgent` proposal, it is possible that the vote will close before delegators have a chance to react and override their validator's vote. This is not a problem, as `Urgent` proposals require more than 2/3rd of the total voting power to pass before the end of the voting period. If more than 2/3rd of validators collude, they can censor the votes of delegators anyway. + +### Validator’s punishment for non-voting + +Validators are required to vote on all proposals to ensure that results have legitimacy. Voting is part of validators' directives and failure to do it will result in a penalty. + +If a validator’s address is not in the list of addresses that voted on a proposal and the vote is closed (i.e. `MinDeposit` was reached and `Voting period` is over), then the validator will automatically be partially slashed of `GovernancePenalty`. + +*Note: Need to define values for `GovernancePenalty`* + +**Exception:** If a proposal is a `Urgent` proposal and is accepted via the special condition of having a ratio of `Yes` votes to `InitTotalVotingPower` that exceeds 2:3, validators cannot be punished for not having voted on it. That is because the proposal will close as soon as the ratio exceeds 2:3, making it mechanically impossible for some validators to vote on it. + +### Governance key and governance address + +Validators can make use of a slot where they can designate a `Governance PubKey`. By default, a validator's `Governance PubKey` will be the same as its main PubKey. Validators can change this `Governance PubKey` by sending a `Change Governance PubKey` transaction signed by their main `Consensus PrivKey`. From there, they will be able to sign votes using the `Governance PrivKey` associated with their `Governance PubKey`. The `Governance PubKey` can be changed at any moment. + + +## Software Upgrade + +If proposals are of type `SoftwareUpgradeProposal`, then nodes need to upgrade their software to the new version that was voted. This process is divided in two steps. + +### Signal + +After a `SoftwareUpgradeProposal` is accepted, validators are expected to download and install the new version of the software while continuing to run the previous version. Once a validator has downloaded and installed the upgrade, it will start signaling to the network that it is ready to switch by including the proposal's `proposalID` in its *precommits*.(*Note: Confirmation that we want it in the precommit?*) + +Note: There is only one signal slot per *precommit*. If several `SoftwareUpgradeProposals` are accepted in a short timeframe, a pipeline will form and they will be implemented one after the other in the order that they were accepted. + +### Switch + +Once a block contains more than 2/3rd *precommits* where a common `SoftwareUpgradeProposal` is signaled, all the nodes (including validator nodes, non-validating full nodes and light-nodes) are expected to switch to the new version of the software. + +*Note: Not clear how the flip is handled programatically* + + +## Implementation + +*Disclaimer: This is a suggestion. Only structs and pseudocode. Actual logic and implementation might widely differ* + +### State + +#### Procedures + +`Procedures` define the rule according to which votes are run. There can only be one active procedure at any given time. If governance wants to change a procedure, either to modify a value or add/remove a parameter, a new procedure has to be created and the previous one rendered inactive. + +```Go +type Procedure struct { + VotingPeriod int64 // Length of the voting period. Initial value: 2 weeks + MinDeposit int64 // Minimum deposit for a proposal to enter voting period. + OptionSet []string // Options available to voters. {Yes, No, NoWithVeto, Abstain} + ProposalTypes []string // Types available to submitters. {PlainTextProposal, SoftwareUpgradeProposal} + Threshold rational.Rational // Minimum propotion of Yes votes for proposal to pass. Initial value: 0.5 + Veto rational.Rational // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3 + MaxDepositPeriod int64 // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months + GovernancePenalty int64 // Penalty if validator does not vote + + IsActive bool // If true, procedure is active. Only one procedure can have isActive true. +} +``` + +**Store**: +- `Procedures`: a mapping `map[int16]Procedure` of procedures indexed by their `ProcedureNumber` +- `ActiveProcedureNumber`: returns current procedure number + +#### Proposals + +`Proposals` are item to be voted on. + +```Go +type Proposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + Deposit int64 // Current deposit on this proposal. Initial value is set at InitialDeposit + SubmitBlock int64 // Height of the block where TxGovSubmitProposal was included + + VotingStartBlock int64 // Height of the block where MinDeposit was reached. -1 if MinDeposit is not reached + InitTotalVotingPower int64 // Total voting power when proposal enters voting period (default 0) + InitProcedureNumber int16 // Procedure number of the active procedure when proposal enters voting period (default -1) + Votes map[string]int64 // Votes for each option (Yes, No, NoWithVeto, Abstain) +} +``` + +We also introduce a type `ValidatorGovInfo` + +```Go +type ValidatorGovInfo struct { + InitVotingPower int64 // Voting power of validator when proposal enters voting period + Minus int64 // Minus of validator, used to compute validator's voting power +} +``` + +**Store:** + +- `Proposals`: A mapping `map[int64]Proposal` of proposals indexed by their `proposalID` +- `Deposits`: A mapping `map[[]byte]int64` of deposits indexed by `:` as `[]byte`. Given a `proposalID` and a `PubKey`, returns deposit (`nil` if `PubKey` has not deposited on the proposal) +- `Options`: A mapping `map[[]byte]string` of options indexed by `::` as `[]byte`. Given a `proposalID`, a `PubKey` and a validator's `PubKey`, returns option chosen by this `PubKey` for this validator (`nil` if `PubKey` has not voted under this validator) +- `ValidatorGovInfos`: A mapping `map[[]byte]ValidatorGovInfo` of validator's governance infos indexed by `:`. Returns `nil` if proposal has not entered voting period or if `PubKey` was not the governance public key of a validator when proposal entered voting period. + + +#### Proposal Processing Queue + +**Store:** +- `ProposalProcessingQueue`: A queue `queue[proposalID]` containing all the `ProposalIDs` of proposals that reached `MinDeposit`. Each round, the oldest element of `ProposalProcessingQueue` is checked during `BeginBlock` to see if `CurrentBlock == VotingStartBlock + InitProcedure.VotingPeriod`. If it is, then the application checks if validators in `InitVotingPowerList` have voted and, if not, applies `GovernancePenalty`. After that proposal is ejected from `ProposalProcessingQueue` and the next element of the queue is evaluated. Note that if a proposal is urgent and accepted under the special condition, its `ProposalID` must be ejected from `ProposalProcessingQueue`. + +And the pseudocode for the `ProposalProcessingQueue`: + +``` + in BeginBlock do + + checkProposal() // First call of the recursive function + + + // Recursive function. First call in BeginBlock + func checkProposal() + if (ProposalProcessingQueue.Peek() == nil) + return + + else + proposalID = ProposalProcessingQueue.Peek() + proposal = load(store, Proposals, proposalID) + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) + + // proposal was urgent and accepted under the special condition + // no punishment + + ProposalProcessingQueue.pop() + checkProposal() + + else if (CurrentBlock == proposal.VotingStartBlock + initProcedure.VotingPeriod) + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + for each validator in CurrentBondedValidators + validatorGovInfo = load(store, ValidatorGovInfos, validator.GovPubKey) + + if (validatorGovInfo.InitVotingPower != nil) + // validator was bonded when vote started + + validatorOption = load(store, Options, validator.GovPubKey) + if (validatorOption == nil) + // validator did not vote + slash validator by activeProcedure.GovernancePenalty + + ProposalProcessingQueue.pop() + checkProposal() +``` + + +### Transactions + +#### Proposal Submission + +Proposals can be submitted by any Atom holder via a `TxGovSubmitProposal` transaction. + +```Go +type TxGovSubmitProposal struct { + Title string // Title of the proposal + Description string // Description of the proposal + Type string // Type of proposal. Initial set {PlainTextProposal, SoftwareUpgradeProposal} + Category bool // false=regular, true=urgent + InitialDeposit int64 // Initial deposit paid by sender. Must be strictly positive. +} +``` + +**State modifications:** +- Generate new `proposalID` +- Create new `Proposal` +- Initialise `Proposals` attributes +- Store sender's deposit in `Deposits` +- Decrease balance of sender by `InitialDeposit` +- If `MinDeposit` is reached: + - Push `proposalID` in `ProposalProcessingQueueEnd` + - Store each validator's voting power in `ValidatorGovInfos` + +A `TxGovSubmitProposal` transaction can be handled according to the following pseudocode + +``` +// PSEUDOCODE // +// Check if TxGovSubmitProposal is valid. If it is, create proposal // + +upon receiving txGovSubmitProposal from sender do + + if !correctlyFormatted(txGovSubmitProposal) then + // check if proposal is correctly formatted. Includes fee payment. + + throw + + else + if (txGovSubmitProposal.InitialDeposit <= 0) OR (sender.AtomBalance < InitialDeposit) then + // InitialDeposit is negative or null OR sender has insufficient funds + + throw + + else + sender.AtomBalance -= txGovSubmitProposal.InitialDeposit + + proposalID = generate new proposalID + proposal = NewProposal() + + proposal.Title = txGovSubmitProposal.Title + proposal.Description = txGovSubmitProposal.Description + proposal.Type = txGovSubmitProposal.Type + proposal.Category = txGovSubmitProposal.Category + proposal.Deposit = txGovSubmitProposal.InitialDeposit + proposal.SubmitBlock = CurrentBlock + + store(Deposits, :, txGovSubmitProposal.InitialDeposit) + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + + if (txGovSubmitProposal.InitialDeposit < activeProcedure.MinDeposit) then + // MinDeposit is not reached + + proposal.VotingStartBlock = -1 + proposal.InitTotalVotingPower = 0 + proposal.InitProcedureNumber = -1 + + else + // MinDeposit is reached + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator + + validatorGovInfo = NewValidatorGovInfo() + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 + + store(ValidatorGovInfos, :, validatorGovInfo) + + ProposalProcessingQueue.push(proposalID) + + store(Proposals, proposalID, proposal) // Store proposal in Proposals mapping + return proposalID +``` + + + +#### Deposit + +Once a proposal is submitted, if `Proposal.Deposit < ActiveProcedure.MinDeposit`, Atom holders can send `TxGovDeposit` transactions to increase the proposal's deposit. + +```Go +type TxGovDeposit struct { + ProposalID int64 // ID of the proposal + Deposit int64 // Number of Atoms to add to the proposal's deposit +} +``` + +**State modifications:** +- Decrease balance of sender by `deposit` +- Initialize or increase `deposit` of sender in `Deposits` +- Increase `proposal.Deposit` by sender's `deposit` +- If `MinDeposit` is reached: + - Push `proposalID` in `ProposalProcessingQueueEnd` + - Store each validator's voting power in `ValidatorGovInfos` + +A `TxGovDeposit` transaction has to go through a number of checks to be valid. These checks are outlined in the following pseudocode. + +``` +// PSEUDOCODE // +// Check if TxGovDeposit is valid. If it is, increase deposit and check if MinDeposit is reached + +upon receiving txGovDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + if (txGovDeposit.Deposit <= 0) OR (sender.AtomBalance < txGovDeposit.Deposit) + // deposit is negative or null OR sender has insufficient funds + + throw + + else + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit was reached + + throw + + else + if (CurrentBlock >= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) then + // Maximum deposit period reached + + throw + + else + // sender can deposit + + sender.AtomBalance -= txGovDeposit.Deposit + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has never deposited on this proposal + + store(Deposits, :, deposit) + + else + // sender has already deposited on this proposal + + newDeposit = deposit + txGovDeposit.Deposit + store(Deposits, :, newDeposit) + + proposal.Deposit += txGovDeposit.Deposit + + if (proposal.Deposit >= activeProcedure.MinDeposit) then + // MinDeposit is reached, vote opens + + proposal.VotingStartBlock = CurrentBlock + proposal.InitTotalVotingPower = TotalVotingPower + proposal.InitProcedureNumber = ActiveProcedureNumber + + for each validator in CurrentBondedValidators + // Store voting power of each bonded validator + + validatorGovInfo = NewValidatorGovInfo() + validatorGovInfo.InitVotingPower = validator.VotingPower + validatorGovInfo.Minus = 0 + + store(ValidatorGovInfos, :, validatorGovInfo) + + ProposalProcessingQueue.push(txGovDeposit.ProposalID) +``` + +#### Claiming deposit + +Finally, if the proposal is accepted or `MinDeposit` was not reached before the end of the `MaximumDepositPeriod`, then Atom holders can send `TxGovClaimDeposit` transaction to claim their deposits. + +```Go + type TxGovClaimDeposit struct { + ProposalID int64 + } +``` + +**State modifications:** +If conditions are met, reimburse the deposit, i.e. +- Increase `AtomBalance` of sender by `deposit` +- Set `deposit` of sender in `DepositorsList` to 0 + +And the associated pseudocode + +``` + // PSEUDOCODE // + /* Check if TxGovClaimDeposit is valid. If vote never started and MaxDepositPeriod is reached or if vote started and proposal was accepted, return deposit */ + + upon receiving txGovClaimDeposit from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovClaimDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + deposit = load(store, Deposits, :) + + if (deposit == nil) + // sender has not deposited on this proposal + + throw + + else + if (deposit <= 0) + // deposit has already been claimed + + throw + + else + if (proposal.VotingStartBlock <= 0) + // Vote never started + + activeProcedure = load(store, Procedures, ActiveProcedureNumber) + if (CurrentBlock <= proposal.SubmitBlock + activeProcedure.MaxDepositPeriod) + // MaxDepositPeriod is not reached + + throw + + else + // MaxDepositPeriod is reached + // Set sender's deposit to 0 and refund + + store(Deposits, :, 0) + sender.AtomBalance += deposit + + else + // Vote started + + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) + + if (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) OR + ((CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) AND (proposal.Votes['NoWithVeto']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) < 1/3) AND (proposal.Votes['Yes']/(proposal.Votes['Yes']+proposal.Votes['No']+proposal.Votes['NoWithVeto']) > 1/2)) then + + // Proposal was accepted either because + // Proposal was urgent and special condition was met + // Voting period ended and vote satisfies threshold + + store(Deposits, :, 0) + sender.AtomBalance += deposit + +``` + + +#### Vote + +Once `ActiveProcedure.MinDeposit` is reached, voting period starts. From there, bonded Atom holders are able to send `TxGovVote` transactions to cast their vote on the proposal. + +```Go + type TxGovVote struct { + ProposalID int64 // proposalID of the proposal + Option string // option from OptionSet chosen by the voter + ValidatorPubKey crypto.PubKey // PubKey of the validator voter wants to tie its vote to + } +``` + +**State modifications:** +- If sender is not a validator and validator has not voted, initialize or increase minus of validator by sender's `voting power` +- If sender is not a validator and validator has voted, decrease `proposal.Votes['validatorOption']` by sender's `voting power` +- If sender is not a validator, increase `[proposal.Votes['txGovVote.Option']` by sender's `voting power` +- If sender is a validator, increase `proposal.Votes['txGovVote.Option']` by validator's `InitialVotingPower - minus` (`minus` can be equal to 0) + +Votes need to be tied to a validator in order to compute validator's voting power. If a delegator is bonded to multiple validators, it will have to send one transaction per validator (the UI should facilitate this so that multiple transactions can be sent in one "vote flow"). +If the sender is the validator itself, then it will input its own GovernancePubKey as `ValidatorPubKey` + + + +Next is a pseudocode proposal of the way `TxGovVote` transactions can be handled: + +``` + // PSEUDOCODE // + // Check if TxGovVote is valid. If it is, count vote// + + upon receiving txGovVote from sender do + // check if proposal is correctly formatted. Includes fee payment. + + if !correctlyFormatted(txGovDeposit) then + throw + + else + proposal = load(store, Proposals, txGovDeposit.ProposalID) + + if (proposal == nil) then + // There is no proposal for this proposalID + + throw + + else + initProcedure = load(store, Procedures, proposal.InitProcedureNumber) // get procedure that was active when vote opened + validator = load(store, Validators, txGovVote.ValidatorPubKey) + + if !initProcedure.OptionSet.includes(txGovVote.Option) OR + (validator == nil) then + + // Throws if + // Option is not in Option Set of procedure that was active when vote opened OR if + // ValidatorPubKey is not the GovPubKey of a current validator + + throw + + else + option = load(store, Options, ::) + + if (option != nil) + // sender has already voted with the Atoms bonded to ValidatorPubKey + + throw + + else + if (proposal.VotingStartBlock < 0) OR + (CurrentBlock > proposal.VotingStartBlock + initProcedure.VotingPeriod) OR + (proposal.VotingStartBlock < lastBondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.VotingStartBlock < lastUnbondingBlock(sender, txGovVote.ValidatorPubKey) OR + (proposal.Category AND proposal.Votes['Yes']/proposal.InitTotalVotingPower >= 2/3) then + + // Throws if + // Vote has not started OR if + // Vote had ended OR if + // sender bonded Atoms to ValidatorPubKey after start of vote OR if + // sender unbonded Atoms from ValidatorPubKey after start of vote OR if + // proposal is urgent and special condition is met, i.e. proposal is accepted and closed + + throw + + else + validatorGovInfo = load(store, ValidatorGovInfos, :) + + if (validatorGovInfo == nil) + // validator became validator after proposal entered voting period + + throw + + else + // sender can vote, check if sender == validator and store sender's option in Options + + store(Options, ::, txGovVote.Option) + + if (sender != validator.GovPubKey) + // Here, sender is not the Governance PubKey of the validator whose PubKey is txGovVote.ValidatorPubKey + + if sender does not have bonded Atoms to txGovVote.ValidatorPubKey then + // check in Staking module + + throw + + else + validatorOption = load(store, Options, :::, validatorGovInfo) + + else + // Validator has already voted + // Reduce votes of option chosen by validator by sender's bonded Amount + + proposal.Votes['validatorOption'] -= sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + // increase votes of option chosen by sender by bonded Amount + proposal.Votes['txGovVote.Option'] += sender.bondedAmountTo(txGovVote.ValidatorPubKey) + + else + // sender is the Governance PubKey of the validator whose main PubKey is txGovVote.ValidatorPubKey + // i.e. sender == validator + + proposal.Votes['txGovVote.Option'] += (validatorGovInfo.InitVotingPower - validatorGovInfo.Minus) + + +``` + + +## Future improvements (not in scope for MVP) + +The current documentation only describes the minimum viable product for the governance module. Future improvements may include: + +- **`BountyProposals`:** If accepted, a `BountyProposal` creates an open bounty. The `BountyProposal` specifies how many Atoms will be given upon completion. These Atoms will be taken from the `reserve pool`. After a `BountyProposal` is accepted by governance, anybody can submit a `SoftwareUpgradeProposal` with the code to claim the bounty. Note that once a `BountyProposal` is accepted, the corresponding funds in the `reserve pool` are locked so that payment can always be honored. In order to link a `SoftwareUpgradeProposal` to an open bounty, the submitter of the `SoftwareUpgradeProposal` will use the `Proposal.LinkedProposal` attribute. If a `SoftwareUpgradeProposal` linked to an open bounty is accepted by governance, the funds that were reserved are automatically transferred to the submitter. +- **Complex delegation:** Delegators could choose other representatives than their validators. Ultimately, the chain of representatives would always end up to a validator, but delegators could inherit the vote of their chosen representative before they inherit the vote of their validator. In other words, they would only inherit the vote of their validator if their other appointed representative did not vote. +- **`ParameterProposals` and `WhitelistProposals`:** These proposals would automatically change pre-defined parameters and whitelists. Upon acceptance, these proposals would not require validators to do the signal and switch process. +- **Better process for proposal review:** There would be two parts to `proposal.Deposit`, one for anti-spam (same as in MVP) and an other one to reward third party auditors. diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 1827b4d98e..7f7377b483 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -68,7 +68,7 @@ func main() { // add proxy, version and key info basecliCmd.AddCommand( client.LineBreak, - lcd.ServeCommand(), + lcd.ServeCommand(cdc), keys.Commands(), client.LineBreak, version.VersionCmd, diff --git a/glide.lock b/glide.lock new file mode 100644 index 0000000000..413737d23a --- /dev/null +++ b/glide.lock @@ -0,0 +1,263 @@ +hash: bff8e6213ad8494602f2095adde9bdbab0fd891345675920175cf05c65702e07 +updated: 2018-03-02T12:01:38.719098766-05:00 +imports: +- name: github.com/bgentry/speakeasy + version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd +- name: github.com/btcsuite/btcd + version: 2be2f12b358dc57d70b8f501b00be450192efbc3 + subpackages: + - btcec +- name: github.com/ebuchman/fail-test + version: 95f809107225be108efcf10a3509e4ea6ceef3c4 +- name: github.com/fsnotify/fsnotify + version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9 +- name: github.com/go-kit/kit + version: 4dc7be5d2d12881735283bcab7352178e190fc71 + subpackages: + - log + - log/level + - log/term +- name: github.com/go-logfmt/logfmt + version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-stack/stack + version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc +- name: github.com/gogo/protobuf + version: 1adfc126b41513cc696b209667c8656ea7aac67c + subpackages: + - gogoproto + - jsonpb + - proto + - protoc-gen-gogo/descriptor + - sortkeys + - types +- name: github.com/golang/protobuf + version: 925541529c1fa6821df4e44ce2723319eb2be768 + subpackages: + - proto + - ptypes + - ptypes/any + - ptypes/duration + - ptypes/timestamp +- name: github.com/golang/snappy + version: 553a641470496b2327abcac10b36396bd98e45c9 +- name: github.com/gorilla/websocket + version: 0647012449a1878977514a346b26637dd022446c +- name: github.com/hashicorp/hcl + version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8 + subpackages: + - hcl/ast + - hcl/parser + - hcl/scanner + - hcl/strconv + - hcl/token + - json/parser + - json/scanner + - json/token +- name: github.com/howeyc/crc16 + version: 2b2a61e366a66d3efb279e46176e7291001e0354 +- name: github.com/inconshreveable/mousetrap + version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 +- name: github.com/jmhodges/levigo + version: c42d9e0ca023e2198120196f842701bb4c55d7b9 +- name: github.com/kr/logfmt + version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 +- name: github.com/magiconair/properties + version: 2c9e9502788518c97fe44e8955cd069417ee89df +- name: github.com/mattn/go-isatty + version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39 +- name: github.com/mitchellh/mapstructure + version: 00c29f56e2386353d58c599509e8dc3801b0d716 +- name: github.com/pelletier/go-toml + version: 05bcc0fb0d3e60da4b8dd5bd7e0ea563eb4ca943 +- name: github.com/pkg/errors + version: 645ef00459ed84a119197bfb8d8205042c6df63d +- name: github.com/rcrowley/go-metrics + version: 8732c616f52954686704c8645fe1a9d59e9df7c1 +- name: github.com/rigelrozanski/common + version: f691f115798593d783b9999b1263c2f4ffecc439 +- name: github.com/spf13/afero + version: bbf41cb36dffe15dff5bf7e18c447801e7ffe163 + subpackages: + - mem +- name: github.com/spf13/cast + version: 8965335b8c7107321228e3e3702cab9832751bac +- name: github.com/spf13/cobra + version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b +- name: github.com/spf13/jwalterweatherman + version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394 +- name: github.com/spf13/pflag + version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 +- name: github.com/spf13/viper + version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 +- name: github.com/syndtr/goleveldb + version: c7a14d4b00e222eab6111b4cd1af829c13f53ec2 + subpackages: + - leveldb + - leveldb/cache + - leveldb/comparer + - leveldb/errors + - leveldb/filter + - leveldb/iterator + - leveldb/journal + - leveldb/memdb + - leveldb/opt + - leveldb/storage + - leveldb/table + - leveldb/util +- name: github.com/tendermint/abci + version: 9e0e00bef42aebf6b402f66bf0f3dc607de8a6f3 + subpackages: + - client + - example/code + - example/kvstore + - server + - types +- name: github.com/tendermint/ed25519 + version: d8387025d2b9d158cf4efb07e7ebf814bcce2057 + subpackages: + - edwards25519 + - extra25519 +- name: github.com/tendermint/go-crypto + version: c3e19f3ea26f5c3357e0bcbb799b0761ef923755 + subpackages: + - keys + - keys/bcrypt + - keys/words + - keys/words/wordlist +- name: github.com/tendermint/go-wire + version: fa721242b042ecd4c6ed1a934ee740db4f74e45c + subpackages: + - data +- name: github.com/tendermint/iavl + version: 669ff61054a14c4542dbd657ab438800d5630e45 +- name: github.com/tendermint/tendermint + version: 3cedd8cf070ef120964ac99367cd69414665604b + 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 + - types/priv_validator + - 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: 91a49db82a88618983a78a06c1cbd4e00ab749ab + subpackages: + - blowfish + - curve25519 + - nacl/box + - nacl/secretbox + - openpgp/armor + - openpgp/errors + - poly1305 + - ripemd160 + - salsa20/salsa +- name: golang.org/x/net + version: 22ae77b79946ea320088417e4d50825671d82d57 + subpackages: + - context + - http2 + - http2/hpack + - idna + - internal/timeseries + - lex/httplex + - netutil + - trace +- name: golang.org/x/sys + version: dd2ff4accc098aceecb86b36eaa7829b2a17b1c9 + subpackages: + - unix +- name: golang.org/x/text + version: 0b0b1f509072617b86d90971b51da23cc52694f2 + subpackages: + - secure/bidirule + - transform + - unicode/bidi + - unicode/norm +- name: google.golang.org/genproto + version: 2c5e7ac708aaa719366570dd82bda44541ca2a63 + subpackages: + - googleapis/rpc/status +- name: google.golang.org/grpc + version: f0a1202acdc5c4702be05098d5ff8e9b3b444442 + subpackages: + - balancer + - balancer/base + - balancer/roundrobin + - codes + - connectivity + - credentials + - encoding + - encoding/proto + - grpclb/grpc_lb_v1/messages + - grpclog + - internal + - keepalive + - metadata + - naming + - peer + - resolver + - resolver/dns + - resolver/passthrough + - stats + - status + - tap + - transport +- name: gopkg.in/yaml.v2 + version: 7f97868eec74b32b0982dd158a51a446d1da7eb5 +testImports: +- name: github.com/davecgh/go-spew + version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73 + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: 792786c7400a136282c1664665ae0a8db921c6c2 + subpackages: + - difflib +- name: github.com/stretchr/testify + version: 12b6f73e6084dad08a7c6e575284b177ecafbc71 + subpackages: + - assert + - require diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000000..5752ee12de --- /dev/null +++ b/glide.yaml @@ -0,0 +1,54 @@ +package: github.com/cosmos/cosmos-sdk +import: +- package: github.com/golang/protobuf + 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 +- package: github.com/tendermint/abci + version: develop + subpackages: + - server + - types +- package: github.com/tendermint/go-crypto + version: v0.5.0 +- package: github.com/tendermint/go-wire + version: v0.7.3 +- package: github.com/tendermint/iavl + version: v0.6.1 +- package: github.com/tendermint/tmlibs + version: develop + subpackages: + - common + - db + - log + - merkle +- package: github.com/tendermint/tendermint + version: develop + subpackages: + - cmd/tendermint/commands + - config + - lite + - rpc/client + - types +- package: golang.org/x/crypto + subpackages: + - ripemd160 +- 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 +testImport: +- package: github.com/stretchr/testify + version: ^1.2.1 + subpackages: + - assert + - require diff --git a/version/command.go b/version/command.go index 9986f605d7..c21ebb9379 100644 --- a/version/command.go +++ b/version/command.go @@ -2,6 +2,7 @@ package version import ( "fmt" + "net/http" "github.com/spf13/cobra" ) @@ -11,14 +12,28 @@ var ( VersionCmd = &cobra.Command{ Use: "version", Short: "Print the app version", - Run: doVersionCmd, + Run: printVersion, } ) -func doVersionCmd(cmd *cobra.Command, args []string) { +func getVersion() string { v := Version if GitCommit != "" { v = v + " " + GitCommit } + return v +} + +// CMD + +func printVersion(cmd *cobra.Command, args []string) { + v := getVersion() fmt.Println(v) } + +// REST + +func VersionRequestHandler(w http.ResponseWriter, r *http.Request) { + v := getVersion() + w.Write([]byte(v)) +} From 027d953bf18d8f1f2388b1d5f9239ab755475eed Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Mon, 5 Mar 2018 14:00:57 +0100 Subject: [PATCH 18/64] added gorrila/mux dependency --- glide.lock | 12 ++++++++---- glide.yaml | 2 ++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/glide.lock b/glide.lock index 413737d23a..8dfe9ecbed 100644 --- a/glide.lock +++ b/glide.lock @@ -1,5 +1,5 @@ -hash: bff8e6213ad8494602f2095adde9bdbab0fd891345675920175cf05c65702e07 -updated: 2018-03-02T12:01:38.719098766-05:00 +hash: 9f09fece8535abc405f5a624312a0cc18316fb9cd35986de63bff28f29343ce8 +updated: 2018-03-05T13:56:23.9966479+01:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd @@ -40,6 +40,10 @@ imports: - ptypes/timestamp - name: github.com/golang/snappy version: 553a641470496b2327abcac10b36396bd98e45c9 +- name: github.com/gorilla/context + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 +- name: github.com/gorilla/mux + version: 53c1911da2b537f792e7cafcb446b05ffe33b996 - name: github.com/gorilla/websocket version: 0647012449a1878977514a346b26637dd022446c - name: github.com/hashicorp/hcl @@ -105,7 +109,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: 9e0e00bef42aebf6b402f66bf0f3dc607de8a6f3 + version: d5361de3001184de7cd3a0ccbae12eccde3d724e subpackages: - client - example/code @@ -131,7 +135,7 @@ imports: - name: github.com/tendermint/iavl version: 669ff61054a14c4542dbd657ab438800d5630e45 - name: github.com/tendermint/tendermint - version: 3cedd8cf070ef120964ac99367cd69414665604b + version: bdd50c5f3750238fbd60948cd1a747f9016e50d9 subpackages: - blockchain - cmd/tendermint/commands diff --git a/glide.yaml b/glide.yaml index 5752ee12de..20c7e3f699 100644 --- a/glide.yaml +++ b/glide.yaml @@ -46,6 +46,8 @@ import: version: v0.0.1 - package: github.com/spf13/viper version: ^1.0.0 +- package: github.com/gorilla/mux + version: ^1.6.1 testImport: - package: github.com/stretchr/testify version: ^1.2.1 From 370d8df82525efb824972e254dd592e61b613a6e Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Mon, 5 Mar 2018 14:57:29 +0100 Subject: [PATCH 19/64] remove vscode config folder --- .gitignore | 1 + .vscode/launch.json | 21 --------------------- 2 files changed, 1 insertion(+), 21 deletions(-) delete mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index b7ce3693bc..e24dd0d2ac 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ examples/basecoin/app/data baseapp/data/* docs/_build .DS_Store +.vscode ### Vagrant ### diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 76977265a3..0000000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // Verwendet IntelliSense zum Ermitteln möglicher Attribute. - // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. - // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "go", - "request": "launch", - "mode": "debug", - "remotePath": "", - "port": 2345, - "host": "127.0.0.1", - "program": "${fileDirname}", - "env": {}, - "args": [], - "showLog": true - } - ] -} \ No newline at end of file From 7d36d953f2ebde9ad5acde27d11793fde125232d Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Mon, 5 Mar 2018 17:41:50 +0100 Subject: [PATCH 20/64] added tests for /keys --- client/keys/update.go | 2 +- client/keys/utils.go | 5 ++ client/lcd/lcd_test.go | 114 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 client/lcd/lcd_test.go diff --git a/client/keys/update.go b/client/keys/update.go index 6bb4d1ac0f..d95be78bfa 100644 --- a/client/keys/update.go +++ b/client/keys/update.go @@ -84,7 +84,7 @@ func UpdateKeyRequestHandler(w http.ResponseWriter, r *http.Request) { // TODO check if account exists and if password is correct err = kb.Update(name, m.OldPassword, m.NewPassword) if err != nil { - w.WriteHeader(500) + w.WriteHeader(401) w.Write([]byte(err.Error())) return } diff --git a/client/keys/utils.go b/client/keys/utils.go index b6a83ec7c1..c6239002ca 100644 --- a/client/keys/utils.go +++ b/client/keys/utils.go @@ -41,6 +41,11 @@ func GetKeyBase() (keys.Keybase, error) { return keybase, nil } +// used to set the keybase manually in test +func SetKeyBase(kb keys.Keybase) { + keybase = kb +} + func printInfo(info keys.Info) { switch viper.Get(cli.OutputFlag) { case "text": diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go new file mode 100644 index 0000000000..7840e5fa16 --- /dev/null +++ b/client/lcd/lcd_test.go @@ -0,0 +1,114 @@ +package lcd + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + cryptoKeys "github.com/tendermint/go-crypto/keys" + dbm "github.com/tendermint/tmlibs/db" +) + +func TestKeys(t *testing.T) { + kb, err := initKeybase() + if err != nil { + t.Errorf("Couldn't init Keybase. Error $s", err.Error()) + } + cdc := app.MakeCodec() + r := initRouter(cdc) + + // empty keys + req, _ := http.NewRequest("GET", "/keys", nil) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) + if body := res.Body.String(); body != "[]" { + t.Errorf("Expected an empty array. Got %s", body) + } + + info, _, err := kb.Create("test", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + if err != nil { + t.Errorf("Couldn't add key. Error $s", err.Error()) + } + + // existing keys + req, _ = http.NewRequest("GET", "/keys", nil) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) + var m [1]keys.KeyOutput + decoder := json.NewDecoder(res.Body) + err = decoder.Decode(&m) + + if m[0].Name != "test" { + t.Errorf("Did not serve keys name correctly. Got %s", m[0].Name) + } + if m[0].Address != info.PubKey.Address().String() { + t.Errorf("Did not serve keys Address correctly. Got %s, Expected %s", m[0].Address, info.PubKey.Address().String()) + } + + // select key + req, _ = http.NewRequest("GET", "/keys/test", nil) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) + var m2 keys.KeyOutput + decoder = json.NewDecoder(res.Body) + err = decoder.Decode(&m2) + + if m2.Name != "test" { + t.Errorf("Did not serve keys name correctly. Got %s", m2.Name) + } + if m2.Address != info.PubKey.Address().String() { + t.Errorf("Did not serve keys Address correctly. Got %s, Expected %s", m2.Address, info.PubKey.Address().String()) + } + + // update key + var jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) + req, _ = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) + + // here it should say unauthorized as we changed the password before + req, _ = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusUnauthorized, res.Code) + + // delete key + jsonStr = []byte(`{"password":"12345678901"}`) + req, _ = http.NewRequest("DELETE", "/keys/test", bytes.NewBuffer(jsonStr)) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) +} + +func initKeybase() (cryptoKeys.Keybase, error) { + os.RemoveAll("./testKeybase") + db, err := dbm.NewGoLevelDB("keys", "./testKeybase") + if err != nil { + return nil, err + } + kb := client.GetKeyBase(db) + keys.SetKeyBase(kb) + return kb, nil +} + +func checkResponseCode(t *testing.T, expected, actual int) { + if expected != actual { + t.Errorf("Expected response code %d. Got %d\n", expected, actual) + } +} From 37bbde837b6fac7426518f94ccc0ef9922494c3c Mon Sep 17 00:00:00 2001 From: Fabian Date: Thu, 8 Mar 2018 14:39:59 +0100 Subject: [PATCH 21/64] added node syncing endpoint --- client/lcd/root.go | 3 ++- client/rpc/status.go | 21 +++++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/client/lcd/root.go b/client/lcd/root.go index cf72087b7d..01a4ccb9e6 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -48,7 +48,8 @@ func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) er func initRouter(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") - r.HandleFunc("/node_info", rpc.NodeStatusRequestHandler).Methods("GET") + r.HandleFunc("/node_info", rpc.NodeInfoRequestHandler).Methods("GET") + r.HandleFunc("/syncing", rpc.NodeSyncingRequestHandler).Methods("GET") r.HandleFunc("/keys", keys.QueryKeysRequestHandler).Methods("GET") r.HandleFunc("/keys", keys.AddNewKeyRequestHandler).Methods("POST") r.HandleFunc("/keys/seed", keys.SeedRequestHandler).Methods("GET") diff --git a/client/rpc/status.go b/client/rpc/status.go index 54926bc3ed..b61d7202db 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "net/http" + "strconv" "github.com/spf13/cobra" wire "github.com/tendermint/go-wire" @@ -51,8 +52,7 @@ func printNodeStatus(cmd *cobra.Command, args []string) error { // REST -// TODO match desired spec output -func NodeStatusRequestHandler(w http.ResponseWriter, r *http.Request) { +func NodeInfoRequestHandler(w http.ResponseWriter, r *http.Request) { status, err := getNodeStatus() if err != nil { w.WriteHeader(500) @@ -69,3 +69,20 @@ func NodeStatusRequestHandler(w http.ResponseWriter, r *http.Request) { } w.Write(output) } + +func NodeSyncingRequestHandler(w http.ResponseWriter, r *http.Request) { + status, err := getNodeStatus() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + syncing := status.Syncing + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + w.Write([]byte(strconv.FormatBool(syncing))) +} From cdba13c531d8b81a845bdf366c693b81d1ae5efe Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Thu, 8 Mar 2018 15:50:12 +0100 Subject: [PATCH 22/64] added node info tests --- client/lcd/.gitignore | 1 + client/lcd/lcd_test.go | 54 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 client/lcd/.gitignore diff --git a/client/lcd/.gitignore b/client/lcd/.gitignore new file mode 100644 index 0000000000..fc27e483f9 --- /dev/null +++ b/client/lcd/.gitignore @@ -0,0 +1 @@ +testKeybase \ No newline at end of file diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 7840e5fa16..1fcac4a554 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -8,15 +8,19 @@ import ( "os" "testing" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + abci "github.com/tendermint/abci/types" cryptoKeys "github.com/tendermint/go-crypto/keys" + "github.com/tendermint/tendermint/p2p" dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/log" ) func TestKeys(t *testing.T) { - kb, err := initKeybase() + kb, db, err := initKeybase() if err != nil { t.Errorf("Couldn't init Keybase. Error $s", err.Error()) } @@ -94,17 +98,59 @@ func TestKeys(t *testing.T) { r.ServeHTTP(res, req) checkResponseCode(t, http.StatusOK, res.Code) + + db.Close() } -func initKeybase() (cryptoKeys.Keybase, error) { +func TestNodeInfo(t *testing.T) { + prepareApp(t) + _, db, err := initKeybase() + if err != nil { + t.Errorf("Couldn't init Keybase. Error $s", err.Error()) + } + cdc := app.MakeCodec() + r := initRouter(cdc) + + req, _ := http.NewRequest("GET", "/node_info", nil) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + checkResponseCode(t, http.StatusOK, res.Code) + + var m p2p.NodeInfo + decoder := json.NewDecoder(res.Body) + err = decoder.Decode(&m) + if err != nil { + t.Errorf("Couldn't parse node info, Got %s", res.Body.String()) + } + + db.Close() +} + +func defaultLogger() log.Logger { + return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") +} + +func prepareApp(t *testing.T) { + logger := defaultLogger() + db := dbm.NewMemDB() + name := t.Name() + app := baseapp.NewBaseApp(name, logger, db) + + header := abci.Header{Height: 1} + app.BeginBlock(abci.RequestBeginBlock{Header: header}) + app.Commit() +} + +func initKeybase() (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { os.RemoveAll("./testKeybase") db, err := dbm.NewGoLevelDB("keys", "./testKeybase") if err != nil { - return nil, err + return nil, nil, err } kb := client.GetKeyBase(db) keys.SetKeyBase(kb) - return kb, nil + return kb, db, nil } func checkResponseCode(t *testing.T, expected, actual int) { From c7589cc67cdbceb589c554cfbb63d77a26cad1d5 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Thu, 8 Mar 2018 15:06:40 +0000 Subject: [PATCH 23/64] lcd tests to assert --- client/lcd/lcd_test.go | 91 ++++++++++++++++++------------------------ 1 file changed, 39 insertions(+), 52 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 1fcac4a554..4d563c6e2d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -12,6 +12,8 @@ import ( "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" cryptoKeys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tendermint/p2p" @@ -20,113 +22,106 @@ import ( ) func TestKeys(t *testing.T) { - kb, db, err := initKeybase() - if err != nil { - t.Errorf("Couldn't init Keybase. Error $s", err.Error()) - } + kb, db, err := initKeybase(t) + require.Nil(t, err, "Couldn't init Keybase") + cdc := app.MakeCodec() r := initRouter(cdc) // empty keys - req, _ := http.NewRequest("GET", "/keys", nil) + req, err := http.NewRequest("GET", "/keys", nil) + require.Nil(t, err) res := httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) - if body := res.Body.String(); body != "[]" { - t.Errorf("Expected an empty array. Got %s", body) - } + assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + body := res.Body.String() + require.Equal(t, body, "[]", "Expected an empty array") info, _, err := kb.Create("test", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - if err != nil { - t.Errorf("Couldn't add key. Error $s", err.Error()) - } + require.Nil(t, err, "Couldn't add key") // existing keys - req, _ = http.NewRequest("GET", "/keys", nil) + req, err = http.NewRequest("GET", "/keys", nil) + require.Nil(t, err) res = httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) + assert.Equal(t, http.StatusOK, res.Code, "Expected response code") var m [1]keys.KeyOutput decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) - if m[0].Name != "test" { - t.Errorf("Did not serve keys name correctly. Got %s", m[0].Name) - } - if m[0].Address != info.PubKey.Address().String() { - t.Errorf("Did not serve keys Address correctly. Got %s, Expected %s", m[0].Address, info.PubKey.Address().String()) - } + assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") + assert.Equal(t, m[0].Address, info.PubKey.Address().String(), "Did not serve keys Address correctly") // select key req, _ = http.NewRequest("GET", "/keys/test", nil) res = httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) + assert.Equal(t, http.StatusOK, res.Code, "Expected response code") var m2 keys.KeyOutput decoder = json.NewDecoder(res.Body) err = decoder.Decode(&m2) - if m2.Name != "test" { - t.Errorf("Did not serve keys name correctly. Got %s", m2.Name) - } - if m2.Address != info.PubKey.Address().String() { - t.Errorf("Did not serve keys Address correctly. Got %s, Expected %s", m2.Address, info.PubKey.Address().String()) - } + assert.Equal(t, m2.Name, "test", "Did not serve keys name correctly") + assert.Equal(t, m2.Address, info.PubKey.Address().String(), "Did not serve keys Address correctly") // update key var jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - req, _ = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + req, err = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + require.Nil(t, err) res = httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) + assert.Equal(t, http.StatusOK, res.Code, "Expected response code") // here it should say unauthorized as we changed the password before - req, _ = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + req, err = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) + require.Nil(t, err) res = httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusUnauthorized, res.Code) + assert.Equal(t, http.StatusUnauthorized, res.Code, "Expected response code") // delete key jsonStr = []byte(`{"password":"12345678901"}`) - req, _ = http.NewRequest("DELETE", "/keys/test", bytes.NewBuffer(jsonStr)) + req, err = http.NewRequest("DELETE", "/keys/test", bytes.NewBuffer(jsonStr)) + require.Nil(t, err) res = httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) + assert.Equal(t, http.StatusOK, res.Code, "Expected response code") db.Close() } func TestNodeInfo(t *testing.T) { prepareApp(t) - _, db, err := initKeybase() - if err != nil { - t.Errorf("Couldn't init Keybase. Error $s", err.Error()) - } + _, db, err := initKeybase(t) + require.Nil(t, err, "Couldn't init Keybase") cdc := app.MakeCodec() r := initRouter(cdc) - req, _ := http.NewRequest("GET", "/node_info", nil) + req, err := http.NewRequest("GET", "/node_info", nil) + require.Nil(t, err) res := httptest.NewRecorder() r.ServeHTTP(res, req) - checkResponseCode(t, http.StatusOK, res.Code) + require.Equal(t, http.StatusOK, res.Code, "Expected response code") var m p2p.NodeInfo decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) - if err != nil { - t.Errorf("Couldn't parse node info, Got %s", res.Body.String()) - } + require.Nil(t, err, "Couldn't parse node info") db.Close() } +//__________________________________________________________ +// helpers + func defaultLogger() log.Logger { return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") } @@ -142,19 +137,11 @@ func prepareApp(t *testing.T) { app.Commit() } -func initKeybase() (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { +func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { os.RemoveAll("./testKeybase") db, err := dbm.NewGoLevelDB("keys", "./testKeybase") - if err != nil { - return nil, nil, err - } + require.Nil(t, err) kb := client.GetKeyBase(db) keys.SetKeyBase(kb) return kb, db, nil } - -func checkResponseCode(t *testing.T, expected, actual int) { - if expected != actual { - t.Errorf("Expected response code %d. Got %d\n", expected, actual) - } -} From a28f53721dfd1ca2bcc6204a0d0e967a20284be3 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 9 Mar 2018 02:37:49 +0000 Subject: [PATCH 24/64] rebase fixes ... --- Gopkg.lock | 12 ++ client/lcd/root.go | 2 +- client/rpc/status.go | 2 +- client/tx/tx.go | 6 +- glide.lock | 267 ------------------------------------------- glide.yaml | 56 --------- 6 files changed, 15 insertions(+), 330 deletions(-) delete mode 100644 glide.lock delete mode 100644 glide.yaml diff --git a/Gopkg.lock b/Gopkg.lock index 5d0691f08f..da17c2efff 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -84,6 +84,18 @@ packages = ["."] revision = "553a641470496b2327abcac10b36396bd98e45c9" +[[projects]] + name = "github.com/gorilla/context" + packages = ["."] + revision = "1ea25387ff6f684839d82767c1733ff4d4d15d0a" + version = "v1.1" + +[[projects]] + name = "github.com/gorilla/mux" + packages = ["."] + revision = "53c1911da2b537f792e7cafcb446b05ffe33b996" + version = "v1.6.1" + [[projects]] name = "github.com/gorilla/websocket" packages = ["."] diff --git a/client/lcd/root.go b/client/lcd/root.go index 01a4ccb9e6..4ebc0cba98 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -6,13 +6,13 @@ import ( "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" - wire "github.com/tendermint/go-wire" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" rpc "github.com/cosmos/cosmos-sdk/client/rpc" tx "github.com/cosmos/cosmos-sdk/client/tx" version "github.com/cosmos/cosmos-sdk/version" + "github.com/cosmos/cosmos-sdk/wire" ) const ( diff --git a/client/rpc/status.go b/client/rpc/status.go index b61d7202db..e5da948692 100644 --- a/client/rpc/status.go +++ b/client/rpc/status.go @@ -40,7 +40,7 @@ func printNodeStatus(cmd *cobra.Command, args []string) error { return err } - output, err := wire.MarshalJSON(res) + output, err := wire.MarshalJSON(status) // output, err := json.MarshalIndent(res, " ", "") if err != nil { return err diff --git a/client/tx/tx.go b/client/tx/tx.go index 1f30798708..7a8d061386 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -11,16 +11,12 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/client" - keybase "github.com/cosmos/cosmos-sdk/client/keys" - sdk "github.com/cosmos/cosmos-sdk/types" abci "github.com/tendermint/abci/types" keys "github.com/tendermint/go-crypto/keys" - wire "github.com/tendermint/go-wire" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" + keybase "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) diff --git a/glide.lock b/glide.lock deleted file mode 100644 index 8dfe9ecbed..0000000000 --- a/glide.lock +++ /dev/null @@ -1,267 +0,0 @@ -hash: 9f09fece8535abc405f5a624312a0cc18316fb9cd35986de63bff28f29343ce8 -updated: 2018-03-05T13:56:23.9966479+01:00 -imports: -- name: github.com/bgentry/speakeasy - version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd -- name: github.com/btcsuite/btcd - version: 2be2f12b358dc57d70b8f501b00be450192efbc3 - subpackages: - - btcec -- name: github.com/ebuchman/fail-test - version: 95f809107225be108efcf10a3509e4ea6ceef3c4 -- name: github.com/fsnotify/fsnotify - version: c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9 -- name: github.com/go-kit/kit - version: 4dc7be5d2d12881735283bcab7352178e190fc71 - subpackages: - - log - - log/level - - log/term -- name: github.com/go-logfmt/logfmt - version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 -- name: github.com/go-stack/stack - version: 259ab82a6cad3992b4e21ff5cac294ccb06474bc -- name: github.com/gogo/protobuf - version: 1adfc126b41513cc696b209667c8656ea7aac67c - subpackages: - - gogoproto - - jsonpb - - proto - - protoc-gen-gogo/descriptor - - sortkeys - - types -- name: github.com/golang/protobuf - version: 925541529c1fa6821df4e44ce2723319eb2be768 - subpackages: - - proto - - ptypes - - ptypes/any - - ptypes/duration - - ptypes/timestamp -- name: github.com/golang/snappy - version: 553a641470496b2327abcac10b36396bd98e45c9 -- name: github.com/gorilla/context - version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 -- name: github.com/gorilla/mux - version: 53c1911da2b537f792e7cafcb446b05ffe33b996 -- name: github.com/gorilla/websocket - version: 0647012449a1878977514a346b26637dd022446c -- name: github.com/hashicorp/hcl - version: 23c074d0eceb2b8a5bfdbb271ab780cde70f05a8 - subpackages: - - hcl/ast - - hcl/parser - - hcl/scanner - - hcl/strconv - - hcl/token - - json/parser - - json/scanner - - json/token -- name: github.com/howeyc/crc16 - version: 2b2a61e366a66d3efb279e46176e7291001e0354 -- name: github.com/inconshreveable/mousetrap - version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 -- name: github.com/jmhodges/levigo - version: c42d9e0ca023e2198120196f842701bb4c55d7b9 -- name: github.com/kr/logfmt - version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 -- name: github.com/magiconair/properties - version: 2c9e9502788518c97fe44e8955cd069417ee89df -- name: github.com/mattn/go-isatty - version: 0360b2af4f38e8d38c7fce2a9f4e702702d73a39 -- name: github.com/mitchellh/mapstructure - version: 00c29f56e2386353d58c599509e8dc3801b0d716 -- name: github.com/pelletier/go-toml - version: 05bcc0fb0d3e60da4b8dd5bd7e0ea563eb4ca943 -- name: github.com/pkg/errors - version: 645ef00459ed84a119197bfb8d8205042c6df63d -- name: github.com/rcrowley/go-metrics - version: 8732c616f52954686704c8645fe1a9d59e9df7c1 -- name: github.com/rigelrozanski/common - version: f691f115798593d783b9999b1263c2f4ffecc439 -- name: github.com/spf13/afero - version: bbf41cb36dffe15dff5bf7e18c447801e7ffe163 - subpackages: - - mem -- name: github.com/spf13/cast - version: 8965335b8c7107321228e3e3702cab9832751bac -- name: github.com/spf13/cobra - version: 7b2c5ac9fc04fc5efafb60700713d4fa609b777b -- name: github.com/spf13/jwalterweatherman - version: 7c0cea34c8ece3fbeb2b27ab9b59511d360fb394 -- name: github.com/spf13/pflag - version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 -- name: github.com/spf13/viper - version: 25b30aa063fc18e48662b86996252eabdcf2f0c7 -- name: github.com/syndtr/goleveldb - version: c7a14d4b00e222eab6111b4cd1af829c13f53ec2 - subpackages: - - leveldb - - leveldb/cache - - leveldb/comparer - - leveldb/errors - - leveldb/filter - - leveldb/iterator - - leveldb/journal - - leveldb/memdb - - leveldb/opt - - leveldb/storage - - leveldb/table - - leveldb/util -- name: github.com/tendermint/abci - version: d5361de3001184de7cd3a0ccbae12eccde3d724e - subpackages: - - client - - example/code - - example/kvstore - - server - - types -- name: github.com/tendermint/ed25519 - version: d8387025d2b9d158cf4efb07e7ebf814bcce2057 - subpackages: - - edwards25519 - - extra25519 -- name: github.com/tendermint/go-crypto - version: c3e19f3ea26f5c3357e0bcbb799b0761ef923755 - subpackages: - - keys - - keys/bcrypt - - keys/words - - keys/words/wordlist -- name: github.com/tendermint/go-wire - version: fa721242b042ecd4c6ed1a934ee740db4f74e45c - subpackages: - - data -- name: github.com/tendermint/iavl - version: 669ff61054a14c4542dbd657ab438800d5630e45 -- name: github.com/tendermint/tendermint - version: bdd50c5f3750238fbd60948cd1a747f9016e50d9 - 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 - - types/priv_validator - - 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: 91a49db82a88618983a78a06c1cbd4e00ab749ab - subpackages: - - blowfish - - curve25519 - - nacl/box - - nacl/secretbox - - openpgp/armor - - openpgp/errors - - poly1305 - - ripemd160 - - salsa20/salsa -- name: golang.org/x/net - version: 22ae77b79946ea320088417e4d50825671d82d57 - subpackages: - - context - - http2 - - http2/hpack - - idna - - internal/timeseries - - lex/httplex - - netutil - - trace -- name: golang.org/x/sys - version: dd2ff4accc098aceecb86b36eaa7829b2a17b1c9 - subpackages: - - unix -- name: golang.org/x/text - version: 0b0b1f509072617b86d90971b51da23cc52694f2 - subpackages: - - secure/bidirule - - transform - - unicode/bidi - - unicode/norm -- name: google.golang.org/genproto - version: 2c5e7ac708aaa719366570dd82bda44541ca2a63 - subpackages: - - googleapis/rpc/status -- name: google.golang.org/grpc - version: f0a1202acdc5c4702be05098d5ff8e9b3b444442 - subpackages: - - balancer - - balancer/base - - balancer/roundrobin - - codes - - connectivity - - credentials - - encoding - - encoding/proto - - grpclb/grpc_lb_v1/messages - - grpclog - - internal - - keepalive - - metadata - - naming - - peer - - resolver - - resolver/dns - - resolver/passthrough - - stats - - status - - tap - - transport -- name: gopkg.in/yaml.v2 - version: 7f97868eec74b32b0982dd158a51a446d1da7eb5 -testImports: -- name: github.com/davecgh/go-spew - version: 8991bc29aa16c548c550c7ff78260e27b9ab7c73 - subpackages: - - spew -- name: github.com/pmezard/go-difflib - version: 792786c7400a136282c1664665ae0a8db921c6c2 - subpackages: - - difflib -- name: github.com/stretchr/testify - version: 12b6f73e6084dad08a7c6e575284b177ecafbc71 - subpackages: - - assert - - require diff --git a/glide.yaml b/glide.yaml deleted file mode 100644 index 20c7e3f699..0000000000 --- a/glide.yaml +++ /dev/null @@ -1,56 +0,0 @@ -package: github.com/cosmos/cosmos-sdk -import: -- package: github.com/golang/protobuf - 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 -- package: github.com/tendermint/abci - version: develop - subpackages: - - server - - types -- package: github.com/tendermint/go-crypto - version: v0.5.0 -- package: github.com/tendermint/go-wire - version: v0.7.3 -- package: github.com/tendermint/iavl - version: v0.6.1 -- package: github.com/tendermint/tmlibs - version: develop - subpackages: - - common - - db - - log - - merkle -- package: github.com/tendermint/tendermint - version: develop - subpackages: - - cmd/tendermint/commands - - config - - lite - - rpc/client - - types -- package: golang.org/x/crypto - subpackages: - - ripemd160 -- 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 -- package: github.com/gorilla/mux - version: ^1.6.1 -testImport: -- package: github.com/stretchr/testify - version: ^1.2.1 - subpackages: - - assert - - require From 425aadfb19a2b43eaaff3c16458b5a4a25489237 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 9 Mar 2018 02:47:26 +0000 Subject: [PATCH 25/64] dep update --- Gopkg.lock | 2 +- Gopkg.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index da17c2efff..c9e3eb67e5 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -343,7 +343,7 @@ revision = "cd2ba4aa7f95e16fe99570260ad58415e7ad4660" [[projects]] - branch = "develop" + branch = "rigel/cli-refactor" name = "github.com/tendermint/tmlibs" packages = [ "autofile", diff --git a/Gopkg.toml b/Gopkg.toml index 1cbd701e21..fe3de57e8e 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -82,8 +82,9 @@ name = "github.com/tendermint/tendermint" [[override]] - branch = "develop" + branch = "rigel/cli-refactor" name = "github.com/tendermint/tmlibs" + #version = "6ef3e36e82ab95739c5beacddca8e931f62d2cef" [prune] go-tests = true From afea775748037ab39c0c3e051112027ebd8a1c0e Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Fri, 9 Mar 2018 10:14:44 +0100 Subject: [PATCH 26/64] improved test output --- client/lcd/lcd_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 4d563c6e2d..e04c54bbe9 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -34,7 +34,7 @@ func TestKeys(t *testing.T) { res := httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) body := res.Body.String() require.Equal(t, body, "[]", "Expected an empty array") @@ -47,7 +47,7 @@ func TestKeys(t *testing.T) { res = httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m [1]keys.KeyOutput decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) @@ -60,7 +60,7 @@ func TestKeys(t *testing.T) { res = httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m2 keys.KeyOutput decoder = json.NewDecoder(res.Body) err = decoder.Decode(&m2) @@ -75,7 +75,7 @@ func TestKeys(t *testing.T) { res = httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) // here it should say unauthorized as we changed the password before req, err = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) @@ -83,7 +83,7 @@ func TestKeys(t *testing.T) { res = httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusUnauthorized, res.Code, "Expected response code") + assert.Equal(t, http.StatusUnauthorized, res.Code, res.Body.String()) // delete key jsonStr = []byte(`{"password":"12345678901"}`) @@ -92,7 +92,7 @@ func TestKeys(t *testing.T) { res = httptest.NewRecorder() r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, "Expected response code") + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) db.Close() } @@ -109,7 +109,7 @@ func TestNodeInfo(t *testing.T) { res := httptest.NewRecorder() r.ServeHTTP(res, req) - require.Equal(t, http.StatusOK, res.Code, "Expected response code") + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m p2p.NodeInfo decoder := json.NewDecoder(res.Body) From 2f470baf192ebff8a611b5126e20c68b64eac455 Mon Sep 17 00:00:00 2001 From: Fabian Date: Fri, 9 Mar 2018 10:15:56 +0100 Subject: [PATCH 27/64] refactored route registering --- client/keys/root.go | 10 ++++++ client/lcd/root.go | 20 +++-------- client/rpc/root.go | 10 ++++++ client/tx/broadcast.go | 33 ++++++++++++++++++ client/tx/{tx.go => query.go} | 65 ----------------------------------- client/tx/root.go | 8 +++++ client/tx/sign.go | 45 ++++++++++++++++++++++++ 7 files changed, 110 insertions(+), 81 deletions(-) create mode 100644 client/tx/broadcast.go rename client/tx/{tx.go => query.go} (69%) create mode 100644 client/tx/sign.go diff --git a/client/keys/root.go b/client/keys/root.go index 962986c91a..48b50d5e97 100644 --- a/client/keys/root.go +++ b/client/keys/root.go @@ -2,6 +2,7 @@ package keys import ( "github.com/cosmos/cosmos-sdk/client" + "github.com/gorilla/mux" "github.com/spf13/cobra" ) @@ -27,3 +28,12 @@ func Commands() *cobra.Command { ) return cmd } + +func RegisterRoutes(r *mux.Router) { + r.HandleFunc("/keys", QueryKeysRequestHandler).Methods("GET") + r.HandleFunc("/keys", AddNewKeyRequestHandler).Methods("POST") + r.HandleFunc("/keys/seed", SeedRequestHandler).Methods("GET") + r.HandleFunc("/keys/{name}", GetKeyRequestHandler).Methods("GET") + r.HandleFunc("/keys/{name}", UpdateKeyRequestHandler).Methods("PUT") + r.HandleFunc("/keys/{name}", DeleteKeyRequestHandler).Methods("DELETE") +} diff --git a/client/lcd/root.go b/client/lcd/root.go index 4ebc0cba98..a692ebd59b 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -48,21 +48,9 @@ func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) er func initRouter(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") - r.HandleFunc("/node_info", rpc.NodeInfoRequestHandler).Methods("GET") - r.HandleFunc("/syncing", rpc.NodeSyncingRequestHandler).Methods("GET") - r.HandleFunc("/keys", keys.QueryKeysRequestHandler).Methods("GET") - r.HandleFunc("/keys", keys.AddNewKeyRequestHandler).Methods("POST") - r.HandleFunc("/keys/seed", keys.SeedRequestHandler).Methods("GET") - r.HandleFunc("/keys/{name}", keys.GetKeyRequestHandler).Methods("GET") - r.HandleFunc("/keys/{name}", keys.UpdateKeyRequestHandler).Methods("PUT") - r.HandleFunc("/keys/{name}", keys.DeleteKeyRequestHandler).Methods("DELETE") - r.HandleFunc("/txs", tx.SearchTxRequestHandler(cdc)).Methods("GET") - r.HandleFunc("/txs/{hash}", tx.QueryTxRequestHandler(cdc)).Methods("GET") - r.HandleFunc("/txs/sign", tx.SignTxRequstHandler).Methods("POST") - r.HandleFunc("/txs/broadcast", tx.BroadcastTxRequestHandler).Methods("POST") - r.HandleFunc("/blocks/latest", rpc.LatestBlockRequestHandler).Methods("GET") - r.HandleFunc("/blocks/{height}", rpc.BlockRequestHandler).Methods("GET") - r.HandleFunc("/validatorsets/latest", rpc.LatestValidatorsetRequestHandler).Methods("GET") - r.HandleFunc("/validatorsets/{height}", rpc.ValidatorsetRequestHandler).Methods("GET") + + keys.RegisterRoutes(r) + rpc.RegisterRoutes(r) + tx.RegisterRoutes(r, cdc) return r } diff --git a/client/rpc/root.go b/client/rpc/root.go index 8acf8ddad1..8b04c044f4 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -1,6 +1,7 @@ package rpc import ( + "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -42,3 +43,12 @@ func initClientCommand() *cobra.Command { cmd.Flags().String(flagValHash, "", "Hash of trusted validator set (hex-encoded)") return cmd } + +func RegisterRoutes(r *mux.Router) { + r.HandleFunc("/node_info", NodeInfoRequestHandler).Methods("GET") + r.HandleFunc("/syncing", NodeSyncingRequestHandler).Methods("GET") + r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET") + r.HandleFunc("/blocks/{height}", BlockRequestHandler).Methods("GET") + r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET") + r.HandleFunc("/validatorsets/{height}", ValidatorsetRequestHandler).Methods("GET") +} diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go new file mode 100644 index 0000000000..477571ea50 --- /dev/null +++ b/client/tx/broadcast.go @@ -0,0 +1,33 @@ +package tx + +import ( + "encoding/json" + "net/http" + + "github.com/cosmos/cosmos-sdk/client" +) + +type BroadcastTxBody struct { + TxBytes string `json="tx"` +} + +func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) { + var m BroadcastTxBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + res, err := client.BroadcastTx([]byte(m.TxBytes)) + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + w.Write([]byte(string(res.Height))) +} diff --git a/client/tx/tx.go b/client/tx/query.go similarity index 69% rename from client/tx/tx.go rename to client/tx/query.go index 7a8d061386..7c8c4d124f 100644 --- a/client/tx/tx.go +++ b/client/tx/query.go @@ -12,11 +12,9 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" abci "github.com/tendermint/abci/types" - keys "github.com/tendermint/go-crypto/keys" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/cosmos/cosmos-sdk/client" - keybase "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -132,66 +130,3 @@ func QueryTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Requ w.Write(output) } } - -// TODO refactor into different files show, sign, broadcast - -type SignTxBody struct { - Name string `json="name"` - Password string `json="password"` - TxBytes string `json="tx"` -} - -func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) { - var kb keys.Keybase - var m SignTxBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) - return - } - - kb, err = keybase.GetKeyBase() - if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) - return - } - - //TODO check if account exists - sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes)) - if err != nil { - w.WriteHeader(403) - w.Write([]byte(err.Error())) - return - } - - w.Write(sig.Bytes()) -} - -type BroadcastTxBody struct { - TxBytes string `json="tx"` -} - -func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) { - var m BroadcastTxBody - - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) - if err != nil { - w.WriteHeader(400) - w.Write([]byte(err.Error())) - return - } - - res, err := client.BroadcastTx([]byte(m.TxBytes)) - if err != nil { - w.WriteHeader(500) - w.Write([]byte(err.Error())) - return - } - - w.Write([]byte(string(res.Height))) -} diff --git a/client/tx/root.go b/client/tx/root.go index 829a35b19c..fd8b3f0975 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -1,6 +1,7 @@ package tx import ( + "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/wire" @@ -19,3 +20,10 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { QueryTxCmd(cmdr), ) } + +func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET") + r.HandleFunc("/txs/{hash}", QueryTxRequestHandler(cdc)).Methods("GET") + r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") + r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") +} diff --git a/client/tx/sign.go b/client/tx/sign.go new file mode 100644 index 0000000000..3a3fff4a0c --- /dev/null +++ b/client/tx/sign.go @@ -0,0 +1,45 @@ +package tx + +import ( + "encoding/json" + "net/http" + + keybase "github.com/cosmos/cosmos-sdk/client/keys" + keys "github.com/tendermint/go-crypto/keys" +) + +type SignTxBody struct { + Name string `json="name"` + Password string `json="password"` + TxBytes string `json="tx"` +} + +func SignTxRequstHandler(w http.ResponseWriter, r *http.Request) { + var kb keys.Keybase + var m SignTxBody + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(400) + w.Write([]byte(err.Error())) + return + } + + kb, err = keybase.GetKeyBase() + if err != nil { + w.WriteHeader(500) + w.Write([]byte(err.Error())) + return + } + + //TODO check if account exists + sig, _, err := kb.Sign(m.Name, m.Password, []byte(m.TxBytes)) + if err != nil { + w.WriteHeader(403) + w.Write([]byte(err.Error())) + return + } + + w.Write(sig.Bytes()) +} From b6c094f1ad8ded7a93b6f4c74ca8879cc62f654b Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Fri, 9 Mar 2018 11:03:15 +0100 Subject: [PATCH 28/64] improved node status tests --- client/lcd/lcd_test.go | 83 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index e04c54bbe9..f4aff252c3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -3,15 +3,23 @@ package lcd import ( "bytes" "encoding/json" + "fmt" + "io/ioutil" "net/http" "net/http/httptest" "os" "testing" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/mock" + "github.com/cosmos/cosmos-sdk/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" abci "github.com/tendermint/abci/types" @@ -97,13 +105,15 @@ func TestKeys(t *testing.T) { db.Close() } -func TestNodeInfo(t *testing.T) { - prepareApp(t) +func TestNodeStatus(t *testing.T) { + startServer(t) + prepareClient(t) _, db, err := initKeybase(t) require.Nil(t, err, "Couldn't init Keybase") cdc := app.MakeCodec() r := initRouter(cdc) + // node info req, err := http.NewRequest("GET", "/node_info", nil) require.Nil(t, err) res := httptest.NewRecorder() @@ -116,6 +126,18 @@ func TestNodeInfo(t *testing.T) { err = decoder.Decode(&m) require.Nil(t, err, "Couldn't parse node info") + assert.NotEqual(t, p2p.NodeInfo{}, m) + + // syncing + req, err = http.NewRequest("GET", "/syncing", nil) + require.Nil(t, err) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.Equal(t, "true", res.Body.String()) + db.Close() } @@ -126,17 +148,66 @@ func defaultLogger() log.Logger { return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") } -func prepareApp(t *testing.T) { - logger := defaultLogger() +func prepareClient(t *testing.T) { db := dbm.NewMemDB() - name := t.Name() - app := baseapp.NewBaseApp(name, logger, db) + app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db) + viper.Set(client.FlagNode, "localhost:46657") header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) app.Commit() } +// 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 startServer(t *testing.T) { + defer setupViper()() + // init server + initCmd := server.InitCmd(mock.GenInitOptions, log.NewNopLogger()) + err := initCmd.RunE(nil, nil) + require.NoError(t, err) + + // start server + viper.Set("with-tendermint", true) + startCmd := server.StartCmd(mock.NewApp, log.NewNopLogger()) + timeout := time.Duration(3) * time.Second + + err = runOrTimeout(startCmd, timeout) + require.NoError(t, err) +} + +// copied from server/start_test.go +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 + } +} + func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { os.RemoveAll("./testKeybase") db, err := dbm.NewGoLevelDB("keys", "./testKeybase") From 134d909cf67ba07cb9190092f017e194dab1d2d6 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Fri, 9 Mar 2018 11:33:11 +0100 Subject: [PATCH 29/64] added failing tests for blocks and validators --- client/lcd/lcd_test.go | 80 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 3 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index f4aff252c3..b7ab2d71b3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -25,6 +25,7 @@ import ( abci "github.com/tendermint/abci/types" cryptoKeys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tendermint/p2p" + ctypes "github.com/tendermint/tendermint/rpc/core/types" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" ) @@ -107,9 +108,8 @@ func TestKeys(t *testing.T) { func TestNodeStatus(t *testing.T) { startServer(t) + // TODO need to kill server after prepareClient(t) - _, db, err := initKeybase(t) - require.Nil(t, err, "Couldn't init Keybase") cdc := app.MakeCodec() r := initRouter(cdc) @@ -137,8 +137,82 @@ func TestNodeStatus(t *testing.T) { require.Equal(t, http.StatusOK, res.Code, res.Body.String()) assert.Equal(t, "true", res.Body.String()) +} - db.Close() +func TestBlock(t *testing.T) { + startServer(t) + // TODO need to kill server after + prepareClient(t) + cdc := app.MakeCodec() + r := initRouter(cdc) + + req, err := http.NewRequest("GET", "/blocks/latest", nil) + require.Nil(t, err) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + var m ctypes.ResultBlock + decoder := json.NewDecoder(res.Body) + err = decoder.Decode(&m) + require.Nil(t, err, "Couldn't parse block") + + assert.NotEqual(t, ctypes.ResultBlock{}, m) + + req, err = http.NewRequest("GET", "/blocks/1", nil) + require.Nil(t, err) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.NotEqual(t, ctypes.ResultBlock{}, m) + + req, err = http.NewRequest("GET", "/blocks/2", nil) + require.Nil(t, err) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusNotFound, res.Code) +} + +func TestValidators(t *testing.T) { + startServer(t) + // TODO need to kill server after + prepareClient(t) + cdc := app.MakeCodec() + r := initRouter(cdc) + + req, err := http.NewRequest("GET", "/validatorsets/latest", nil) + require.Nil(t, err) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + var m ctypes.ResultValidators + decoder := json.NewDecoder(res.Body) + err = decoder.Decode(&m) + require.Nil(t, err, "Couldn't parse block") + + assert.NotEqual(t, ctypes.ResultValidators{}, m) + + req, err = http.NewRequest("GET", "/validatorsets/1", nil) + require.Nil(t, err) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.NotEqual(t, ctypes.ResultValidators{}, m) + + req, err = http.NewRequest("GET", "/validatorsets/2", nil) + require.Nil(t, err) + res = httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusNotFound, res.Code) } //__________________________________________________________ From 189ce0d73f9f0436e2f6a1822c81bf535e987f50 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Fri, 9 Mar 2018 11:54:19 +0100 Subject: [PATCH 30/64] added version test --- client/lcd/lcd_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b7ab2d71b3..22c9923846 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -106,6 +106,27 @@ func TestKeys(t *testing.T) { db.Close() } +func TestVersion(t *testing.T) { + prepareClient(t) + cdc := app.MakeCodec() + r := initRouter(cdc) + + // node info + req, err := http.NewRequest("GET", "/version", nil) + require.Nil(t, err) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // TODO fix regexp + // reg, err := regexp.Compile(`v\d+\.\d+\.\d+(-dev)?`) + // require.Nil(t, err) + // match := reg.MatchString(res.Body.String()) + // assert.True(t, match, res.Body.String()) + assert.Equal(t, "0.11.1-dev", res.Body.String()) +} + func TestNodeStatus(t *testing.T) { startServer(t) // TODO need to kill server after From 11fdd831ee9f97d70898de90c599c6f17fb46b97 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Fri, 9 Mar 2018 18:36:24 +0000 Subject: [PATCH 31/64] refactoring server new non-deterministic error :( --- Gopkg.toml | 1 - client/lcd/lcd_test.go | 71 ++++++++++-------------------------------- server/init_test.go | 18 +---------- server/start_test.go | 37 ++++++---------------- 4 files changed, 28 insertions(+), 99 deletions(-) diff --git a/Gopkg.toml b/Gopkg.toml index fe3de57e8e..1025288c95 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -84,7 +84,6 @@ [[override]] branch = "rigel/cli-refactor" name = "github.com/tendermint/tmlibs" - #version = "6ef3e36e82ab95739c5beacddca8e931f62d2cef" [prune] go-tests = true diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 22c9923846..16793c85d2 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -3,31 +3,28 @@ package lcd import ( "bytes" "encoding/json" - "fmt" "io/ioutil" "net/http" "net/http/httptest" "os" "testing" - "time" - "github.com/spf13/cobra" "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/mock" - "github.com/cosmos/cosmos-sdk/server" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + abci "github.com/tendermint/abci/types" cryptoKeys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/server" ) func TestKeys(t *testing.T) { @@ -128,9 +125,10 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - startServer(t) - // TODO need to kill server after + ch := server.StartServer(t) + defer close(ch) prepareClient(t) + cdc := app.MakeCodec() r := initRouter(cdc) @@ -147,7 +145,7 @@ func TestNodeStatus(t *testing.T) { err = decoder.Decode(&m) require.Nil(t, err, "Couldn't parse node info") - assert.NotEqual(t, p2p.NodeInfo{}, m) + assert.NotEqual(t, p2p.NodeInfo{}, m, "res: %v", res) // syncing req, err = http.NewRequest("GET", "/syncing", nil) @@ -161,9 +159,10 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - startServer(t) - // TODO need to kill server after + ch := server.StartServer(t) + defer close(ch) prepareClient(t) + cdc := app.MakeCodec() r := initRouter(cdc) @@ -199,8 +198,9 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - startServer(t) - // TODO need to kill server after + ch := server.StartServer(t) + defer close(ch) + prepareClient(t) cdc := app.MakeCodec() r := initRouter(cdc) @@ -266,43 +266,6 @@ func setupViper() func() { } } -func startServer(t *testing.T) { - defer setupViper()() - // init server - initCmd := server.InitCmd(mock.GenInitOptions, log.NewNopLogger()) - err := initCmd.RunE(nil, nil) - require.NoError(t, err) - - // start server - viper.Set("with-tendermint", true) - startCmd := server.StartCmd(mock.NewApp, log.NewNopLogger()) - timeout := time.Duration(3) * time.Second - - err = runOrTimeout(startCmd, timeout) - require.NoError(t, err) -} - -// copied from server/start_test.go -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 - } -} - func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { os.RemoveAll("./testKeybase") db, err := dbm.NewGoLevelDB("keys", "./testKeybase") diff --git a/server/init_test.go b/server/init_test.go index 0af1ecc118..0abb180400 100644 --- a/server/init_test.go +++ b/server/init_test.go @@ -1,11 +1,8 @@ package server import ( - "io/ioutil" - "os" "testing" - "github.com/spf13/viper" "github.com/stretchr/testify/require" "github.com/tendermint/tmlibs/log" @@ -13,21 +10,8 @@ import ( "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()() + defer setupViper(t)() logger := log.NewNopLogger() cmd := InitCmd(mock.GenInitOptions, logger) diff --git a/server/start_test.go b/server/start_test.go index 5b7ab3e76c..5bb2eccbbc 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -1,12 +1,10 @@ package server import ( - "fmt" "os" "testing" "time" - "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -15,7 +13,7 @@ import ( ) func TestStartStandAlone(t *testing.T) { - defer setupViper()() + defer setupViper(t)() logger := log.NewNopLogger() initCmd := InitCmd(mock.GenInitOptions, logger) @@ -26,14 +24,15 @@ func TestStartStandAlone(t *testing.T) { viper.Set(flagWithTendermint, false) viper.Set(flagAddress, "localhost:11122") startCmd := StartCmd(mock.NewApp, logger) + startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second - err = runOrTimeout(startCmd, timeout) - require.NoError(t, err) + ch := RunOrTimeout(startCmd, timeout, t) + close(ch) } func TestStartWithTendermint(t *testing.T) { - defer setupViper()() + defer setupViper(t)() logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "mock-cmd") @@ -45,28 +44,12 @@ func TestStartWithTendermint(t *testing.T) { // set up app and start up viper.Set(flagWithTendermint, true) startCmd := StartCmd(mock.NewApp, logger) + startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second - err = runOrTimeout(startCmd, timeout) - require.NoError(t, err) -} + //a, _ := startCmd.Flags().GetString(flagAddress) + //panic(a) -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 - } + ch := RunOrTimeout(startCmd, timeout, t) + close(ch) } From 8aec2543339f481436b0c30c1d9efde9eb4b8191 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 17:15:56 +0100 Subject: [PATCH 32/64] add add key test --- client/keys/add.go | 4 +-- client/lcd/lcd_test.go | 69 +++++++++++++++++++++++++++++--------- docs/sdk/lcd-rest-api.yaml | 2 +- 3 files changed, 57 insertions(+), 18 deletions(-) diff --git a/client/keys/add.go b/client/keys/add.go index cfaf526460..29908bf9ab 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -164,14 +164,14 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { } algo := keys.CryptoAlgo(m.Type) - _, _, err = kb.Create(m.Name, m.Password, algo) + info, _, err := kb.Create(m.Name, m.Password, algo) // TODO handle different errors if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) return } - w.WriteHeader(200) + w.Write([]byte(info.PubKey.Address().String())) } // function to just a new seed to display in the UI before actually persisting it in the keybase diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 16793c85d2..b2595fa0da 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -28,8 +28,7 @@ import ( ) func TestKeys(t *testing.T) { - kb, db, err := initKeybase(t) - require.Nil(t, err, "Couldn't init Keybase") + prepareClient(t) cdc := app.MakeCodec() r := initRouter(cdc) @@ -44,8 +43,9 @@ func TestKeys(t *testing.T) { body := res.Body.String() require.Equal(t, body, "[]", "Expected an empty array") - info, _, err := kb.Create("test", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - require.Nil(t, err, "Couldn't add key") + // add key + addr := createKey(t, r) + assert.Len(t, addr, 40, "Returned address has wrong format", res.Body.String()) // existing keys req, err = http.NewRequest("GET", "/keys", nil) @@ -59,7 +59,7 @@ func TestKeys(t *testing.T) { err = decoder.Decode(&m) assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") - assert.Equal(t, m[0].Address, info.PubKey.Address().String(), "Did not serve keys Address correctly") + assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") // select key req, _ = http.NewRequest("GET", "/keys/test", nil) @@ -72,7 +72,7 @@ func TestKeys(t *testing.T) { err = decoder.Decode(&m2) assert.Equal(t, m2.Name, "test", "Did not serve keys name correctly") - assert.Equal(t, m2.Address, info.PubKey.Address().String(), "Did not serve keys Address correctly") + assert.Equal(t, m2.Address, addr, "Did not serve keys Address correctly") // update key var jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) @@ -99,8 +99,6 @@ func TestKeys(t *testing.T) { r.ServeHTTP(res, req) assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - db.Close() } func TestVersion(t *testing.T) { @@ -266,11 +264,52 @@ func setupViper() func() { } } -func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { - os.RemoveAll("./testKeybase") - db, err := dbm.NewGoLevelDB("keys", "./testKeybase") - require.Nil(t, err) - kb := client.GetKeyBase(db) - keys.SetKeyBase(kb) - return kb, db, nil +func startServer(t *testing.T) { + defer setupViper()() + // init server + initCmd := server.InitCmd(mock.GenInitOptions, log.NewNopLogger()) + err := initCmd.RunE(nil, nil) + require.NoError(t, err) + + // start server + viper.Set("with-tendermint", true) + startCmd := server.StartCmd(mock.NewApp, log.NewNopLogger()) + timeout := time.Duration(3) * time.Second + + err = runOrTimeout(startCmd, timeout) + require.NoError(t, err) +} + +// copied from server/start_test.go +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 + } +} + +func createKey(t *testing.T, r http.Handler) string { + var jsonStr = []byte(`{"name":"test", "password":"1234567890"}`) + req, err := http.NewRequest("POST", "/keys", bytes.NewBuffer(jsonStr)) + require.Nil(t, err) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + addr := res.Body.String() + return addr } diff --git a/docs/sdk/lcd-rest-api.yaml b/docs/sdk/lcd-rest-api.yaml index 08264d5646..b4ab401e53 100644 --- a/docs/sdk/lcd-rest-api.yaml +++ b/docs/sdk/lcd-rest-api.yaml @@ -62,7 +62,7 @@ paths: summary: Create a new account locally responses: 200: - description: OK + description: Returns address of the account created requestBody: content: application/json: From f1cdf57a48580a0b3600b5503a3d866f3468843c Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 17:35:21 +0100 Subject: [PATCH 33/64] add request test helper --- client/lcd/lcd_test.go | 140 ++++++++++++++++------------------------- 1 file changed, 54 insertions(+), 86 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b2595fa0da..451507656b 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -28,45 +28,36 @@ import ( ) func TestKeys(t *testing.T) { - prepareClient(t) + _, db, err := initKeybase(t) + require.Nil(t, err, "Couldn't init Keybase") cdc := app.MakeCodec() r := initRouter(cdc) // empty keys - req, err := http.NewRequest("GET", "/keys", nil) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res := request(t, r, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) body := res.Body.String() - require.Equal(t, body, "[]", "Expected an empty array") + assert.Equal(t, body, "[]", "Expected an empty array") // add key addr := createKey(t, r) assert.Len(t, addr, 40, "Returned address has wrong format", res.Body.String()) // existing keys - req, err = http.NewRequest("GET", "/keys", nil) - require.Nil(t, err) - res = httptest.NewRecorder() - - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res = request(t, r, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m [1]keys.KeyOutput decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) + require.NoError(t, err) assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") // select key - req, _ = http.NewRequest("GET", "/keys/test", nil) - res = httptest.NewRecorder() - - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res = request(t, r, "GET", "/keys/test", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m2 keys.KeyOutput decoder = json.NewDecoder(res.Body) err = decoder.Decode(&m2) @@ -76,29 +67,19 @@ func TestKeys(t *testing.T) { // update key var jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - req, err = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) - require.Nil(t, err) - res = httptest.NewRecorder() - - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res = request(t, r, "PUT", "/keys/test", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) // here it should say unauthorized as we changed the password before - req, err = http.NewRequest("PUT", "/keys/test", bytes.NewBuffer(jsonStr)) - require.Nil(t, err) - res = httptest.NewRecorder() - - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusUnauthorized, res.Code, res.Body.String()) + res = request(t, r, "PUT", "/keys/test", jsonStr) + require.Equal(t, http.StatusUnauthorized, res.Code, res.Body.String()) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - req, err = http.NewRequest("DELETE", "/keys/test", bytes.NewBuffer(jsonStr)) - require.Nil(t, err) - res = httptest.NewRecorder() + res = request(t, r, "DELETE", "/keys/test", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - r.ServeHTTP(res, req) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + db.Close() } func TestVersion(t *testing.T) { @@ -107,11 +88,7 @@ func TestVersion(t *testing.T) { r := initRouter(cdc) // node info - req, err := http.NewRequest("GET", "/version", nil) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) + res := request(t, r, "GET", "/version", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) // TODO fix regexp @@ -131,26 +108,18 @@ func TestNodeStatus(t *testing.T) { r := initRouter(cdc) // node info - req, err := http.NewRequest("GET", "/node_info", nil) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) + res := request(t, r, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m p2p.NodeInfo decoder := json.NewDecoder(res.Body) - err = decoder.Decode(&m) + err := decoder.Decode(&m) require.Nil(t, err, "Couldn't parse node info") assert.NotEqual(t, p2p.NodeInfo{}, m, "res: %v", res) // syncing - req, err = http.NewRequest("GET", "/syncing", nil) - require.Nil(t, err) - res = httptest.NewRecorder() - - r.ServeHTTP(res, req) + res = request(t, r, "GET", "/syncing", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) assert.Equal(t, "true", res.Body.String()) @@ -164,35 +133,27 @@ func TestBlock(t *testing.T) { cdc := app.MakeCodec() r := initRouter(cdc) - req, err := http.NewRequest("GET", "/blocks/latest", nil) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) + res := request(t, r, "GET", "/blocks/latest", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m ctypes.ResultBlock decoder := json.NewDecoder(res.Body) - err = decoder.Decode(&m) + err := decoder.Decode(&m) require.Nil(t, err, "Couldn't parse block") assert.NotEqual(t, ctypes.ResultBlock{}, m) - req, err = http.NewRequest("GET", "/blocks/1", nil) - require.Nil(t, err) - res = httptest.NewRecorder() + // -- - r.ServeHTTP(res, req) + res = request(t, r, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) assert.NotEqual(t, ctypes.ResultBlock{}, m) - req, err = http.NewRequest("GET", "/blocks/2", nil) - require.Nil(t, err) - res = httptest.NewRecorder() + // -- - r.ServeHTTP(res, req) - require.Equal(t, http.StatusNotFound, res.Code) + res = request(t, r, "GET", "/blocks/2", nil) + require.Equal(t, http.StatusNotFound, res.Code, res.Body.String()) } func TestValidators(t *testing.T) { @@ -203,34 +164,26 @@ func TestValidators(t *testing.T) { cdc := app.MakeCodec() r := initRouter(cdc) - req, err := http.NewRequest("GET", "/validatorsets/latest", nil) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) + res := request(t, r, "GET", "/validatorsets/latest", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m ctypes.ResultValidators decoder := json.NewDecoder(res.Body) - err = decoder.Decode(&m) - require.Nil(t, err, "Couldn't parse block") + err := decoder.Decode(&m) + require.Nil(t, err, "Couldn't parse validatorset") assert.NotEqual(t, ctypes.ResultValidators{}, m) - req, err = http.NewRequest("GET", "/validatorsets/1", nil) - require.Nil(t, err) - res = httptest.NewRecorder() + // -- - r.ServeHTTP(res, req) + res = request(t, r, "GET", "/validatorsets/1", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) assert.NotEqual(t, ctypes.ResultValidators{}, m) - req, err = http.NewRequest("GET", "/validatorsets/2", nil) - require.Nil(t, err) - res = httptest.NewRecorder() + // -- - r.ServeHTTP(res, req) + res = request(t, r, "GET", "/validatorsets/2", nil) require.Equal(t, http.StatusNotFound, res.Code) } @@ -303,13 +256,28 @@ func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error { func createKey(t *testing.T, r http.Handler) string { var jsonStr = []byte(`{"name":"test", "password":"1234567890"}`) - req, err := http.NewRequest("POST", "/keys", bytes.NewBuffer(jsonStr)) - require.Nil(t, err) - res := httptest.NewRecorder() + res := request(t, r, "POST", "/keys", jsonStr) - r.ServeHTTP(res, req) assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) addr := res.Body.String() return addr } + +func request(t *testing.T, r http.Handler, method string, path string, payload []byte) *httptest.ResponseRecorder { + req, err := http.NewRequest(method, path, bytes.NewBuffer(payload)) + require.Nil(t, err) + res := httptest.NewRecorder() + + r.ServeHTTP(res, req) + return res +} + +func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { + os.RemoveAll("./testKeybase") + db, err := dbm.NewGoLevelDB("keys", "./testKeybase") + require.Nil(t, err) + kb := client.GetKeyBase(db) + keys.SetKeyBase(kb) + return kb, db, nil +} From f8a34094bfff535835717a0a8c4b6cf2518c6317 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Sat, 10 Mar 2018 17:27:21 +0000 Subject: [PATCH 34/64] test_helper.go --- server/test_helpers.go | 80 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 server/test_helpers.go diff --git a/server/test_helpers.go b/server/test_helpers.go new file mode 100644 index 0000000000..279583f060 --- /dev/null +++ b/server/test_helpers.go @@ -0,0 +1,80 @@ +package server + +import ( + "fmt" + "io/ioutil" + "net" + "os" + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/mock" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/stretchr/testify/require" + "github.com/tendermint/tmlibs/cli" + "github.com/tendermint/tmlibs/log" +) + +// Get a free address for a test tendermint server +func FreeAddr(t *testing.T) string { + l, err := net.Listen("tcp", "0.0.0.0:0") + defer l.Close() + require.Nil(t, err) + + port := l.Addr().(*net.TCPAddr).Port + addr := fmt.Sprintf("tcp://0.0.0.0:%d", port) + return addr +} + +// setupViper creates a homedir to run inside, +// and returns a cleanup function to defer +func setupViper(t *testing.T) func() { + rootDir, err := ioutil.TempDir("", "mock-sdk-cmd") + require.Nil(t, err) + viper.Set(cli.HomeFlag, rootDir) + return func() { + os.RemoveAll(rootDir) + } +} + +// Begin the server pass up the channel to close +// NOTE pass up the channel so it can be closed at the end of the process +func StartServer(t *testing.T) chan error { + defer setupViper(t)() + + // init server + initCmd := InitCmd(mock.GenInitOptions, log.NewNopLogger()) + err := initCmd.RunE(nil, nil) + require.NoError(t, err) + + // start server + viper.Set(flagWithTendermint, true) + startCmd := StartCmd(mock.NewApp, log.NewNopLogger()) + startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address + timeout := time.Duration(3) * time.Second + + return RunOrTimeout(startCmd, timeout, t) +} + +// Run or Timout RunE of command passed in +func RunOrTimeout(cmd *cobra.Command, timeout time.Duration, t *testing.T) chan 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: + require.NoError(t, err) + case <-timer.C: + return done + } + return done +} From 4ef129d9e654aed86fda77923e8aba7d1873d754 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 18:31:52 +0100 Subject: [PATCH 35/64] require seed for account creation --- client/keys/add.go | 20 +++++++++++--------- client/lcd/lcd_test.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/client/keys/add.go b/client/keys/add.go index 29908bf9ab..19d363850f 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -129,9 +129,7 @@ func printCreate(info keys.Info, seed string) { type NewKeyBody struct { Name string `json:"name"` Password string `json:"password"` - // TODO make seed mandatory - // Seed string `json="seed"` - Type string `json:"type"` + Seed string `json="seed"` } func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { @@ -157,14 +155,18 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("You have to specify a name for the locally stored account.")) return } - - // algo type defaults to ed25519 - if m.Type == "" { - m.Type = "ed25519" + if m.Password == "" { + w.WriteHeader(400) + w.Write([]byte("You have to specify a password for the locally stored account.")) + return + } + if m.Seed == "" { + w.WriteHeader(400) + w.Write([]byte("You have to specify a seed for the locally stored account.")) + return } - algo := keys.CryptoAlgo(m.Type) - info, _, err := kb.Create(m.Name, m.Password, algo) + info, err := kb.Recover(m.Name, m.Password, m.Seed) // TODO handle different errors if err != nil { w.WriteHeader(500) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 451507656b..024e2bbd93 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -7,9 +7,18 @@ import ( "net/http" "net/http/httptest" "os" + "regexp" "testing" "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + "github.com/cosmos/cosmos-sdk/mock" + "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -40,9 +49,27 @@ func TestKeys(t *testing.T) { body := res.Body.String() assert.Equal(t, body, "[]", "Expected an empty array") + // get seed + res = request(t, r, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + seed := res.Body.String() + reg, err := regexp.Compile(`([a-z]+ ){12}`) + require.Nil(t, err) + match := reg.MatchString(seed) + assert.True(t, match, "Returned seed has wrong foramt", seed) + // add key - addr := createKey(t, r) - assert.Len(t, addr, 40, "Returned address has wrong format", res.Body.String()) + var jsonStr = []byte(`{"name":"test_fail", "password":"1234567890"}`) + res = request(t, r, "POST", "/keys", jsonStr) + + assert.Equal(t, http.StatusBadRequest, res.Code, "Account creation should require a seed") + + jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + res = request(t, r, "POST", "/keys", jsonStr) + + assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) + addr := res.Body.String() + assert.Len(t, addr, 40, "Returned address has wrong format", addr) // existing keys res = request(t, r, "GET", "/keys", nil) @@ -66,7 +93,7 @@ func TestKeys(t *testing.T) { assert.Equal(t, m2.Address, addr, "Did not serve keys Address correctly") // update key - var jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) + jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) res = request(t, r, "PUT", "/keys/test", jsonStr) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) From 0d423ae0669fb99a87460b209d1bceedca74d6e1 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 18:32:13 +0100 Subject: [PATCH 36/64] ignore generated keys.db from tests --- client/lcd/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/lcd/.gitignore b/client/lcd/.gitignore index fc27e483f9..5225ad9d69 100644 --- a/client/lcd/.gitignore +++ b/client/lcd/.gitignore @@ -1 +1 @@ -testKeybase \ No newline at end of file +keys.db \ No newline at end of file From 0121c98a1e1298f993ec56061ab6fa8e950e3ef2 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 18:33:05 +0100 Subject: [PATCH 37/64] add /accounts endpoints but failing --- client/lcd/lcd_test.go | 114 ++++++++++++++++++++++++++++++------- client/lcd/root.go | 4 ++ x/auth/commands/account.go | 4 +- x/auth/rest/query.go | 66 +++++++++++++++++++++ x/auth/rest/root.go | 11 ++++ x/bank/commands/sendtx.go | 71 +++++++++++++++-------- x/bank/rest/root.go | 10 ++++ x/bank/rest/sendtx.go | 96 +++++++++++++++++++++++++++++++ 8 files changed, 329 insertions(+), 47 deletions(-) create mode 100644 x/auth/rest/query.go create mode 100644 x/auth/rest/root.go create mode 100644 x/bank/rest/root.go create mode 100644 x/bank/rest/sendtx.go diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 024e2bbd93..e2428c273f 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -127,8 +127,8 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() @@ -153,8 +153,8 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() @@ -184,9 +184,8 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) - + _, _ = startServer(t) + // TODO need to kill server after prepareClient(t) cdc := app.MakeCodec() r := initRouter(cdc) @@ -214,6 +213,67 @@ func TestValidators(t *testing.T) { require.Equal(t, http.StatusNotFound, res.Code) } +func TestCoinSend(t *testing.T) { + addr, seed := startServer(t) + // TODO need to kill server after + prepareClient(t) + cdc := app.MakeCodec() + r := initRouter(cdc) + + // query empty + res := request(t, r, "GET", "/accounts/1234567890123456789012345678901234567890", nil) + require.Equal(t, http.StatusNoContent, res.Code, res.Body.String()) + + // query + res = request(t, r, "GET", "/accounts/"+addr.String(), nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.Equal(t, `{ + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + }`, res.Body.String()) + + // create account for default coins + var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + res = request(t, r, "POST", "/keys", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // create random account + res = request(t, r, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + receiveSeed := res.Body.String() + + jsonStr = []byte(fmt.Sprintf(`{"name":"receive", "password":"1234567890", "seed": "%s"}`, receiveSeed)) + res = request(t, r, "POST", "/keys", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + receiveAddr := res.Body.String() + + // send + jsonStr = []byte(`{"name":"test", "password":"1234567890", "amount":[{ + "denom": "mycoin", + "amount": 1 + }]}`) + res = request(t, r, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // check if received + res = request(t, r, "GET", "/accounts/"+receiveAddr, nil) + require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + assert.Equal(t, `{ + "coins": [ + { + "denom": "mycoin", + "amount": 1 + } + ] + }`, res.Body.String()) +} + //__________________________________________________________ // helpers @@ -226,6 +286,8 @@ func prepareClient(t *testing.T) { app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db) viper.Set(client.FlagNode, "localhost:46657") + _ = client.GetKeyBase(db) + header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) app.Commit() @@ -244,11 +306,31 @@ func setupViper() func() { } } -func startServer(t *testing.T) { +// from baseoind.main +func defaultOptions(addr string) func(args []string) (json.RawMessage, error) { + return func(args []string) (json.RawMessage, error) { + opts := fmt.Sprintf(`{ + "accounts": [{ + "address": "%s", + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + }] + }`, addr) + return json.RawMessage(opts), nil + } +} + +func startServer(t *testing.T) (types.Address, string) { defer setupViper()() // init server - initCmd := server.InitCmd(mock.GenInitOptions, log.NewNopLogger()) - err := initCmd.RunE(nil, nil) + addr, secret, err := server.GenerateCoinKey() + require.NoError(t, err) + initCmd := server.InitCmd(defaultOptions(addr.String()), log.NewNopLogger()) + err = initCmd.RunE(nil, nil) require.NoError(t, err) // start server @@ -258,6 +340,8 @@ func startServer(t *testing.T) { err = runOrTimeout(startCmd, timeout) require.NoError(t, err) + + return addr, secret } // copied from server/start_test.go @@ -281,16 +365,6 @@ func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error { } } -func createKey(t *testing.T, r http.Handler) string { - var jsonStr = []byte(`{"name":"test", "password":"1234567890"}`) - res := request(t, r, "POST", "/keys", jsonStr) - - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - addr := res.Body.String() - return addr -} - func request(t *testing.T, r http.Handler, method string, path string, payload []byte) *httptest.ResponseRecorder { req, err := http.NewRequest(method, path, bytes.NewBuffer(payload)) require.Nil(t, err) diff --git a/client/lcd/root.go b/client/lcd/root.go index a692ebd59b..7be79ced07 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -13,6 +13,8 @@ import ( tx "github.com/cosmos/cosmos-sdk/client/tx" version "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + bank "github.com/cosmos/cosmos-sdk/x/bank/rest" ) const ( @@ -52,5 +54,7 @@ func initRouter(cdc *wire.Codec) http.Handler { keys.RegisterRoutes(r) rpc.RegisterRoutes(r) tx.RegisterRoutes(r, cdc) + auth.RegisterRoutes(r, cdc, "main") + bank.RegisterRoutes(r, cdc) return r } diff --git a/x/auth/commands/account.go b/x/auth/commands/account.go index e9e1a5438f..b6eb51896d 100644 --- a/x/auth/commands/account.go +++ b/x/auth/commands/account.go @@ -16,10 +16,10 @@ import ( // GetAccountCmd for the auth.BaseAccount type func GetAccountCmdDefault(storeName string, cdc *wire.Codec) *cobra.Command { - return GetAccountCmd(storeName, cdc, getParseAccount(cdc)) + return GetAccountCmd(storeName, cdc, GetParseAccount(cdc)) } -func getParseAccount(cdc *wire.Codec) sdk.ParseAccount { +func GetParseAccount(cdc *wire.Codec) sdk.ParseAccount { return func(accBytes []byte) (sdk.Account, error) { acct := new(auth.BaseAccount) err := cdc.UnmarshalBinary(accBytes, &acct) diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go new file mode 100644 index 0000000000..1536e692a5 --- /dev/null +++ b/x/auth/rest/query.go @@ -0,0 +1,66 @@ +package rest + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "net/http" + + "github.com/gorilla/mux" + + "github.com/cosmos/cosmos-sdk/client" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" +) + +type commander struct { + storeName string + cdc *wire.Codec + parser sdk.ParseAccount +} + +func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.ParseAccount) func(http.ResponseWriter, *http.Request) { + c := commander{storeName, cdc, parser} + return func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + addr := vars["address"] + bz, err := hex.DecodeString(addr) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + key := sdk.Address(bz) + + res, err := client.Query(key, c.storeName) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) + return + } + + // the query will return empty if there is no data for this account + if len(res) == 0 { + w.WriteHeader(http.StatusNoContent) + return + } + + // parse out the value + account, err := c.parser(res) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't parse query result. Result: %s. Error: %s", res, err.Error()))) + return + } + + // print out whole account + output, err := json.MarshalIndent(account, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(fmt.Sprintf("Could't marshall query result. Error: %s", err.Error()))) + return + } + + w.Write(output) + } +} diff --git a/x/auth/rest/root.go b/x/auth/rest/root.go new file mode 100644 index 0000000000..c6d6fd9ed7 --- /dev/null +++ b/x/auth/rest/root.go @@ -0,0 +1,11 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/commands" + "github.com/gorilla/mux" +) + +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, storeName string) { + r.HandleFunc("/accounts/{address}", QueryAccountRequestHandler(storeName, cdc, auth.GetParseAccount(cdc))).Methods("GET") +} diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 7e6dd463dd..5b692acec5 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -10,8 +10,8 @@ import ( "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/bank" + cryptokeys "github.com/tendermint/go-crypto/keys" ) const ( @@ -20,8 +20,8 @@ const ( ) // SendTxCommand will create a send tx and sign it with the given key -func SendTxCmd(cdc *wire.Codec) *cobra.Command { - cmdr := commander{cdc} +func SendTxCmd(Cdc *wire.Codec) *cobra.Command { + cmdr := Commander{Cdc} cmd := &cobra.Command{ Use: "send", Short: "Create and sign a send tx", @@ -32,18 +32,32 @@ func SendTxCmd(cdc *wire.Codec) *cobra.Command { return cmd } -type commander struct { - cdc *wire.Codec +type Commander struct { + Cdc *wire.Codec } -func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { - +func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { // get the from address from, err := builder.GetFromAddress() if err != nil { return err } + // parse coins + amount := viper.GetString(flagAmount) + coins, err := sdk.ParseCoins(amount) + if err != nil { + return err + } + + // parse destination address + dest := viper.GetString(flagTo) + bz, err := hex.DecodeString(dest) + if err != nil { + return err + } + to := sdk.Address(bz) + // build send msg msg, err := buildMsg(from) if err != nil { @@ -60,25 +74,32 @@ func (c commander) sendTxCmd(cmd *cobra.Command, args []string) error { return nil } -func buildMsg(from sdk.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 := sdk.Address(bz) - +func BuildMsg(from sdk.Address, to sdk.Address, coins sdk.Coins) sdk.Msg { input := bank.NewInput(from, coins) output := bank.NewOutput(to, coins) msg := bank.NewSendMsg([]bank.Input{input}, []bank.Output{output}) - return msg, nil + return msg +} + +func (c Commander) SignMessage(msg sdk.Msg, kb cryptokeys.Keybase, accountName string, password string) ([]byte, error) { + // sign and build + bz := msg.GetSignBytes() + sig, pubkey, err := kb.Sign(accountName, password, 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 } diff --git a/x/bank/rest/root.go b/x/bank/rest/root.go new file mode 100644 index 0000000000..67c05ab112 --- /dev/null +++ b/x/bank/rest/root.go @@ -0,0 +1,10 @@ +package rest + +import ( + "github.com/cosmos/cosmos-sdk/wire" + "github.com/gorilla/mux" +) + +func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { + r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc)).Methods("POST") +} diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go new file mode 100644 index 0000000000..7ce99dadd8 --- /dev/null +++ b/x/bank/rest/sendtx.go @@ -0,0 +1,96 @@ +package rest + +import ( + "encoding/hex" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + + "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/wire" + "github.com/cosmos/cosmos-sdk/x/bank/commands" +) + +type SendBody struct { + // fees is not used currently + // Fees sdk.Coin `json="fees"` + Amount sdk.Coins `json="amount"` + LocalAccountName string `json="account"` + Password string `json="password"` +} + +func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { + c := commands.Commander{cdc} + return func(w http.ResponseWriter, r *http.Request) { + // collect data + vars := mux.Vars(r) + address := vars["address"] + + var m SendBody + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&m) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + kb, err := keys.GetKeyBase() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + info, err := kb.Get(m.LocalAccountName) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + bz, err := hex.DecodeString(address) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + to := sdk.Address(bz) + + // build + msg := commands.BuildMsg(info.Address(), to, m.Amount) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + // sign + txBytes, err := c.SignMessage(msg, kb, m.LocalAccountName, m.Password) + if err != nil { + w.WriteHeader(http.StatusUnauthorized) + w.Write([]byte(err.Error())) + return + } + + // send + res, err := client.BroadcastTx(txBytes) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + output, err := json.MarshalIndent(res, "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return + } + + w.Write(output) + } +} From ef88f34d5708efbae1bedb9e5a2495fb22184106 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Sat, 10 Mar 2018 18:36:15 +0100 Subject: [PATCH 38/64] added regexp for version returned --- client/lcd/lcd_test.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index e2428c273f..b9b561dbdd 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -118,12 +118,10 @@ func TestVersion(t *testing.T) { res := request(t, r, "GET", "/version", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - // TODO fix regexp - // reg, err := regexp.Compile(`v\d+\.\d+\.\d+(-dev)?`) - // require.Nil(t, err) - // match := reg.MatchString(res.Body.String()) - // assert.True(t, match, res.Body.String()) - assert.Equal(t, "0.11.1-dev", res.Body.String()) + reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match := reg.MatchString(res.Body.String()) + assert.True(t, match, res.Body.String()) } func TestNodeStatus(t *testing.T) { From 1b78752311f0a90762619fb36d1195cdd7f3c620 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Sat, 10 Mar 2018 18:38:49 +0100 Subject: [PATCH 39/64] removed clutter --- client/lcd/keys.db/LOCK | 0 client/lcd/keys.db/LOG | 6 ++++++ client/lcd/lcd_test.go | 1 - 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 client/lcd/keys.db/LOCK create mode 100644 client/lcd/keys.db/LOG diff --git a/client/lcd/keys.db/LOCK b/client/lcd/keys.db/LOCK new file mode 100644 index 0000000000..e69de29bb2 diff --git a/client/lcd/keys.db/LOG b/client/lcd/keys.db/LOG new file mode 100644 index 0000000000..f3b9c6b936 --- /dev/null +++ b/client/lcd/keys.db/LOG @@ -0,0 +1,6 @@ +=============== Mar 10, 2018 (CET) =============== +17:43:54.696390 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +=============== Mar 10, 2018 (CET) =============== +17:45:27.981144 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed +=============== Mar 10, 2018 (CET) =============== +17:46:23.011679 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b9b561dbdd..055108a819 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -3,7 +3,6 @@ package lcd import ( "bytes" "encoding/json" - "io/ioutil" "net/http" "net/http/httptest" "os" From abfa409d37ca54e32c62149c6fc55e52eb3dee9c Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 19:54:14 +0100 Subject: [PATCH 40/64] fixed merge --- client/lcd/lcd_test.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 055108a819..b6948051f0 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -3,6 +3,7 @@ package lcd import ( "bytes" "encoding/json" + "fmt" "net/http" "net/http/httptest" "os" @@ -15,9 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/mock" "github.com/cosmos/cosmos-sdk/server" - "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -27,12 +26,6 @@ import ( ctypes "github.com/tendermint/tendermint/rpc/core/types" dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" - - "github.com/cosmos/cosmos-sdk/baseapp" - "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/server" ) func TestKeys(t *testing.T) { @@ -211,18 +204,22 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - addr, seed := startServer(t) - // TODO need to kill server after + ch := server.StartServer(t) + defer close(ch) + prepareClient(t) cdc := app.MakeCodec() r := initRouter(cdc) + addr := "some address in genesis" + seed := "some seed of a address in genesis" + // query empty res := request(t, r, "GET", "/accounts/1234567890123456789012345678901234567890", nil) require.Equal(t, http.StatusNoContent, res.Code, res.Body.String()) // query - res = request(t, r, "GET", "/accounts/"+addr.String(), nil) + res = request(t, r, "GET", "/accounts/"+addr, nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) assert.Equal(t, `{ From e8e1e2899b40c92a3aba638e3d2d7e788ca6f437 Mon Sep 17 00:00:00 2001 From: Fabian Date: Sat, 10 Mar 2018 20:14:13 +0100 Subject: [PATCH 41/64] removed /latest endpoints and checks --- client/lcd/lcd_test.go | 42 +++++++++++++++++++++++++--------------- client/rpc/block.go | 12 ++++++------ client/rpc/root.go | 4 ++-- client/rpc/validators.go | 12 ++++++------ 4 files changed, 40 insertions(+), 30 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b6948051f0..11d04f9cdb 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -150,7 +150,19 @@ func TestBlock(t *testing.T) { cdc := app.MakeCodec() r := initRouter(cdc) - res := request(t, r, "GET", "/blocks/latest", nil) + // res := request(t, r, "GET", "/blocks/latest", nil) + // require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // var m ctypes.ResultBlock + // decoder := json.NewDecoder(res.Body) + // err := decoder.Decode(&m) + // require.Nil(t, err, "Couldn't parse block") + + // assert.NotEqual(t, ctypes.ResultBlock{}, m) + + // -- + + res := request(t, r, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m ctypes.ResultBlock @@ -162,13 +174,6 @@ func TestBlock(t *testing.T) { // -- - res = request(t, r, "GET", "/blocks/1", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - assert.NotEqual(t, ctypes.ResultBlock{}, m) - - // -- - res = request(t, r, "GET", "/blocks/2", nil) require.Equal(t, http.StatusNotFound, res.Code, res.Body.String()) } @@ -180,7 +185,19 @@ func TestValidators(t *testing.T) { cdc := app.MakeCodec() r := initRouter(cdc) - res := request(t, r, "GET", "/validatorsets/latest", nil) + // res := request(t, r, "GET", "/validatorsets/latest", nil) + // require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + + // var m ctypes.ResultValidators + // decoder := json.NewDecoder(res.Body) + // err := decoder.Decode(&m) + // require.Nil(t, err, "Couldn't parse validatorset") + + // assert.NotEqual(t, ctypes.ResultValidators{}, m) + + // -- + + res := request(t, r, "GET", "/validatorsets/1", nil) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) var m ctypes.ResultValidators @@ -192,13 +209,6 @@ func TestValidators(t *testing.T) { // -- - res = request(t, r, "GET", "/validatorsets/1", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - assert.NotEqual(t, ctypes.ResultValidators{}, m) - - // -- - res = request(t, r, "GET", "/validatorsets/2", nil) require.Equal(t, http.StatusNotFound, res.Code) } diff --git a/client/rpc/block.go b/client/rpc/block.go index 7f197051a9..12c18aa4c2 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -101,12 +101,12 @@ func BlockRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'.")) return } - chainHeight, err := GetChainHeight() - if height > chainHeight { - w.WriteHeader(404) - w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) - return - } + // chainHeight, err := GetChainHeight() + // if height > chainHeight { + // w.WriteHeader(404) + // w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + // return + // } output, err := getBlock(&height) if err != nil { w.WriteHeader(500) diff --git a/client/rpc/root.go b/client/rpc/root.go index 8b04c044f4..03c964070e 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -47,8 +47,8 @@ func initClientCommand() *cobra.Command { func RegisterRoutes(r *mux.Router) { r.HandleFunc("/node_info", NodeInfoRequestHandler).Methods("GET") r.HandleFunc("/syncing", NodeSyncingRequestHandler).Methods("GET") - r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET") + // r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET") r.HandleFunc("/blocks/{height}", BlockRequestHandler).Methods("GET") - r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET") + // r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET") r.HandleFunc("/validatorsets/{height}", ValidatorsetRequestHandler).Methods("GET") } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index 32c7680ec6..c7beeaa4e8 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -78,12 +78,12 @@ func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'.")) return } - chainHeight, err := GetChainHeight() - if height > chainHeight { - w.WriteHeader(404) - w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) - return - } + // chainHeight, err := GetChainHeight() + // if height > chainHeight { + // w.WriteHeader(404) + // w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + // return + // } output, err := getValidators(&height) if err != nil { w.WriteHeader(500) From 8cd9e05fc21f715197b577f4db2426baab282631 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Sat, 10 Mar 2018 20:16:18 +0100 Subject: [PATCH 42/64] remove keys.db --- client/lcd/keys.db/LOCK | 0 client/lcd/keys.db/LOG | 6 ------ 2 files changed, 6 deletions(-) delete mode 100644 client/lcd/keys.db/LOCK delete mode 100644 client/lcd/keys.db/LOG diff --git a/client/lcd/keys.db/LOCK b/client/lcd/keys.db/LOCK deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/client/lcd/keys.db/LOG b/client/lcd/keys.db/LOG deleted file mode 100644 index f3b9c6b936..0000000000 --- a/client/lcd/keys.db/LOG +++ /dev/null @@ -1,6 +0,0 @@ -=============== Mar 10, 2018 (CET) =============== -17:43:54.696390 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -=============== Mar 10, 2018 (CET) =============== -17:45:27.981144 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed -=============== Mar 10, 2018 (CET) =============== -17:46:23.011679 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed From 5cc0acf274b18cc3f68961f82035096e5caa719c Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Sat, 10 Mar 2018 20:27:02 +0100 Subject: [PATCH 43/64] improved send coins test --- client/lcd/lcd_test.go | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 11d04f9cdb..aff2eb736c 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -221,8 +221,11 @@ func TestCoinSend(t *testing.T) { cdc := app.MakeCodec() r := initRouter(cdc) - addr := "some address in genesis" - seed := "some seed of a address in genesis" + // TODO make that account has coins + kb := client.MockKeyBase() + info, seed, err := kb.Create("account_with_coins", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + require.NoError(t, err) + addr := string(info.Address()) // query empty res := request(t, r, "GET", "/accounts/1234567890123456789012345678901234567890", nil) @@ -241,26 +244,25 @@ func TestCoinSend(t *testing.T) { ] }`, res.Body.String()) - // create account for default coins + // create account to send in keybase var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) res = request(t, r, "POST", "/keys", jsonStr) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - // create random account - res = request(t, r, "GET", "/keys/seed", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - receiveSeed := res.Body.String() - - jsonStr = []byte(fmt.Sprintf(`{"name":"receive", "password":"1234567890", "seed": "%s"}`, receiveSeed)) - res = request(t, r, "POST", "/keys", jsonStr) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - receiveAddr := res.Body.String() + // create receive address + receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + require.NoError(t, err) + receiveAddr := string(receiveInfo.Address()) // send - jsonStr = []byte(`{"name":"test", "password":"1234567890", "amount":[{ - "denom": "mycoin", - "amount": 1 - }]}`) + jsonStr = []byte(`{ + "name":"test", + "password":"1234567890", + "amount":[{ + "denom": "mycoin", + "amount": 1 + }] + }`) res = request(t, r, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) require.Equal(t, http.StatusOK, res.Code, res.Body.String()) From 8858371c433f286301470cb4f0f8d1c08e8e9ce9 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 12 Mar 2018 13:10:30 +0100 Subject: [PATCH 44/64] ... --- client/keys.go | 1 + client/lcd/lcd_test.go | 19 ++-- client/lcd/root.go | 3 +- tests/basecli_test.go | 222 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 236 insertions(+), 9 deletions(-) create mode 100644 tests/basecli_test.go diff --git a/client/keys.go b/client/keys.go index 42eb00b7eb..7e150de7a2 100644 --- a/client/keys.go +++ b/client/keys.go @@ -6,6 +6,7 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) +// TODO explain what the keybase is used for // GetKeyBase initializes a keybase based on the configuration func GetKeyBase(db dbm.DB) keys.Keybase { keybase := keys.New( diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index aff2eb736c..898e3926eb 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -117,12 +117,17 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - _, _ = startServer(t) - // TODO need to kill server after - prepareClient(t) + //ch := server.StartServer(t) + //defer close(ch) + //prepareClient(t) - cdc := app.MakeCodec() - r := initRouter(cdc) + //cdc := app.MakeCodec() // TODO refactor so that cdc is included from a common test init function + //r := initRouter(cdc) + + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) // node info res := request(t, r, "GET", "/node_info", nil) @@ -291,9 +296,6 @@ func prepareClient(t *testing.T) { db := dbm.NewMemDB() app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db) viper.Set(client.FlagNode, "localhost:46657") - - _ = client.GetKeyBase(db) - header := abci.Header{Height: 1} app.BeginBlock(abci.RequestBeginBlock{Header: header}) app.Commit() @@ -372,6 +374,7 @@ func runOrTimeout(cmd *cobra.Command, timeout time.Duration) error { } func request(t *testing.T, r http.Handler, method string, path string, payload []byte) *httptest.ResponseRecorder { + req, err := http.NewRequest(method, path, bytes.NewBuffer(payload)) require.Nil(t, err) res := httptest.NewRecorder() diff --git a/client/lcd/root.go b/client/lcd/root.go index 7be79ced07..8476d16d5e 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -51,10 +51,11 @@ func initRouter(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") + // TODO make more functional? aka r = keys.RegisterRoutes(r) keys.RegisterRoutes(r) rpc.RegisterRoutes(r) tx.RegisterRoutes(r, cdc) - auth.RegisterRoutes(r, cdc, "main") + auth.RegisterRoutes(r, cdc, "main") // TODO should use a variable not just a string bank.RegisterRoutes(r, cdc) return r } diff --git a/tests/basecli_test.go b/tests/basecli_test.go new file mode 100644 index 0000000000..8baebfae36 --- /dev/null +++ b/tests/basecli_test.go @@ -0,0 +1,222 @@ +package main + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + //"strings" + "testing" + "time" +) + +// Tests assume the `basecoind` and `basecli` binaries +// have been built and are located in `./build` + +// TODO remove test dirs if tests are successful + +var ( + basecoind = "build/basecoind" + basecli = "build/basecli" + + basecoindDir = "./tmp-basecoind-tests" + basecliDir = "./tmp-basecli-tests" + + ACCOUNTS = []string{"alice", "bob", "charlie", "igor"} + alice = ACCOUNTS[0] + bob = ACCOUNTS[1] + charlie = ACCOUNTS[2] + igor = ACCOUNTS[3] +) + +func gopath() string { + return filepath.Join(os.Getenv("GOPATH"), "src", "github.com", "cosmos", "cosmos-sdk") +} + +func whereIsBasecoind() string { + return filepath.Join(gopath(), basecoind) +} + +func whereIsBasecli() string { + return filepath.Join(gopath(), basecli) +} + +func TestInitBaseCoin(t *testing.T) { + Clean() + + var err error + + password := "some-random-password" + usePassword := exec.Command("echo", password) + + initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir) + + initBasecoind.Stdin, err = usePassword.StdoutPipe() + if err != nil { + t.Error(err) + } + + initBasecoind.Stdout = os.Stdout + + if err := initBasecoind.Start(); err != nil { + t.Error(err) + } + if err := usePassword.Run(); err != nil { + t.Error(err) + } + if err := initBasecoind.Wait(); err != nil { + t.Error(err) + } + + if err := makeKeys(); err != nil { + t.Error(err) + } +} + +func makeKeys() error { + var err error + for _, acc := range ACCOUNTS { + pass := exec.Command("echo", "1234567890") + makeKeys := exec.Command(whereIsBasecli(), "keys", "add", acc, "--home", basecliDir) + + makeKeys.Stdin, err = pass.StdoutPipe() + if err != nil { + return err + } + + makeKeys.Stdout = os.Stdout + if err := makeKeys.Start(); err != nil { + return err + } + if err := pass.Run(); err != nil { + return err + } + + if err := makeKeys.Wait(); err != nil { + return err + } + } + + return nil +} + +// these are in the original bash tests +func TestBaseCliRecover(t *testing.T) {} +func TestBaseCliShow(t *testing.T) {} + +func _TestSendCoins(t *testing.T) { + if err := StartServer(); err != nil { + t.Error(err) + } + + // send some coins + // [zr] where dafuq do I get a FROM (oh, use --name) + + sendTo := fmt.Sprintf("--to=%s", bob) + sendFrom := fmt.Sprintf("--from=%s", alice) + + cmdOut, err := exec.Command(whereIsBasecli(), "send", sendTo, "--amount=1000mycoin", sendFrom, "--seq=0").Output() + if err != nil { + t.Error(err) + } + + fmt.Printf("sent: %s", string(cmdOut)) + +} + +// expects TestInitBaseCoin to have been run +func StartServer() error { + // straight outta https://nathanleclaire.com/blog/2014/12/29/shelled-out-commands-in-golang/ + cmdName := whereIsBasecoind() + cmdArgs := []string{"start", "--home", basecoindDir} + + cmd := exec.Command(cmdName, cmdArgs...) + cmdReader, err := cmd.StdoutPipe() + if err != nil { + return err + } + + scanner := bufio.NewScanner(cmdReader) + go func() { + for scanner.Scan() { + fmt.Printf("running [basecoind start] %s\n", scanner.Text()) + } + }() + + err = cmd.Start() + if err != nil { + return err + } + + err = cmd.Wait() + if err != nil { + return err + } + + time.Sleep(5 * time.Second) + + return nil + + // TODO return cmd.Process so that we can later do something like: + // cmd.Process.Kill() + // see: https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang +} + +func Clean() { + // ignore errors b/c the dirs may not yet exist + os.Remove(basecoindDir) + os.Remove(basecliDir) +} + +/* + + chainID = "staking_test" + testDir = "./tmp_tests" +) + +func runTests() { + + if err := os.Mkdir(testDir, 0666); err != nil { + panic(err) + } + defer os.Remove(testDir) + + // make some keys + + //if err := makeKeys(); err != nil { + // panic(err) + //} + + if err := initServer(); err != nil { + fmt.Printf("Err: %v", err) + panic(err) + } + +} + +func initServer() error { + serveDir := filepath.Join(testDir, "server") + //serverLog := filepath.Join(testDir, "gaia-node.log") + + // get RICH + keyOut, err := exec.Command(GAIA, CLIENT_EXE, "keys", "get", "alice").Output() + if err != nil { + fmt.Println("one") + return err + } + key := strings.Split(string(keyOut), "\t") + fmt.Printf("wit:%s", key[2]) + + outByte, err := exec.Command(GAIA, SERVER_EXE, "init", "--static", fmt.Sprintf("--chain-id=%s", chainID), fmt.Sprintf("--home=%s", serveDir), key[2]).Output() + if err != nil { + fmt.Println("teo") + fmt.Printf("Error: %v", err) + + return err + } + fmt.Sprintf("OUT: %s", string(outByte)) + return nil +} + +*/ From b3532e2d2b3a6b9e14291b7cc9d8e10ea83f3736 Mon Sep 17 00:00:00 2001 From: Fabian Date: Mon, 12 Mar 2018 13:49:29 +0100 Subject: [PATCH 45/64] using actual running servers + http calls to test REST --- client/lcd/lcd_test.go | 263 ++++++++++------------------ tests/{basecli_test.go => tests.go} | 0 2 files changed, 92 insertions(+), 171 deletions(-) rename tests/{basecli_test.go => tests.go} (100%) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 898e3926eb..b9b4d9993d 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -4,8 +4,8 @@ import ( "bytes" "encoding/json" "fmt" + "io/ioutil" "net/http" - "net/http/httptest" "os" "regexp" "testing" @@ -15,8 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/examples/basecoin/app" - "github.com/cosmos/cosmos-sdk/server" + "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -29,22 +28,20 @@ import ( ) func TestKeys(t *testing.T) { - _, db, err := initKeybase(t) - require.Nil(t, err, "Couldn't init Keybase") - - cdc := app.MakeCodec() - r := initRouter(cdc) + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) // empty keys - res := request(t, r, "GET", "/keys", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - body := res.Body.String() + res, body := request(t, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, body, "[]", "Expected an empty array") // get seed - res = request(t, r, "GET", "/keys/seed", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - seed := res.Body.String() + res, body = request(t, "GET", "/keys/seed", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + seed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) match := reg.MatchString(seed) @@ -52,20 +49,20 @@ func TestKeys(t *testing.T) { // add key var jsonStr = []byte(`{"name":"test_fail", "password":"1234567890"}`) - res = request(t, r, "POST", "/keys", jsonStr) + res, body = request(t, "POST", "/keys", jsonStr) - assert.Equal(t, http.StatusBadRequest, res.Code, "Account creation should require a seed") + assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res = request(t, r, "POST", "/keys", jsonStr) + res, body = request(t, "POST", "/keys", jsonStr) - assert.Equal(t, http.StatusOK, res.Code, res.Body.String()) - addr := res.Body.String() + assert.Equal(t, http.StatusOK, res.StatusCode, body) + addr := body assert.Len(t, addr, 40, "Returned address has wrong format", addr) // existing keys - res = request(t, r, "GET", "/keys", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) var m [1]keys.KeyOutput decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) @@ -75,8 +72,8 @@ func TestKeys(t *testing.T) { assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") // select key - res = request(t, r, "GET", "/keys/test", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "GET", "/keys/test", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput decoder = json.NewDecoder(res.Body) err = decoder.Decode(&m2) @@ -86,52 +83,46 @@ func TestKeys(t *testing.T) { // update key jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - res = request(t, r, "PUT", "/keys/test", jsonStr) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "PUT", "/keys/test", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) // here it should say unauthorized as we changed the password before - res = request(t, r, "PUT", "/keys/test", jsonStr) - require.Equal(t, http.StatusUnauthorized, res.Code, res.Body.String()) + res, body = request(t, "PUT", "/keys/test", jsonStr) + require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - res = request(t, r, "DELETE", "/keys/test", jsonStr) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "DELETE", "/keys/test", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) db.Close() } func TestVersion(t *testing.T) { - prepareClient(t) - cdc := app.MakeCodec() - r := initRouter(cdc) - - // node info - res := request(t, r, "GET", "/version", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) - - reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) - require.Nil(t, err) - match := reg.MatchString(res.Body.String()) - assert.True(t, match, res.Body.String()) -} - -func TestNodeStatus(t *testing.T) { - //ch := server.StartServer(t) - //defer close(ch) - //prepareClient(t) - - //cdc := app.MakeCodec() // TODO refactor so that cdc is included from a common test init function - //r := initRouter(cdc) - err := tests.InitServer() require.Nil(t, err) err := tests.StartServer() require.Nil(t, err) // node info - res := request(t, r, "GET", "/node_info", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body := request(t, "GET", "/version", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) + require.Nil(t, err) + match := reg.MatchString(body) + assert.True(t, match, body) +} + +func TestNodeStatus(t *testing.T) { + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) + + // node info + res, body := request(t, "GET", "/node_info", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) var m p2p.NodeInfo decoder := json.NewDecoder(res.Body) @@ -141,22 +132,20 @@ func TestNodeStatus(t *testing.T) { assert.NotEqual(t, p2p.NodeInfo{}, m, "res: %v", res) // syncing - res = request(t, r, "GET", "/syncing", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "GET", "/syncing", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "true", res.Body.String()) + assert.Equal(t, "true", body) } func TestBlock(t *testing.T) { - _, _ = startServer(t) - // TODO need to kill server after - prepareClient(t) + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) - cdc := app.MakeCodec() - r := initRouter(cdc) - - // res := request(t, r, "GET", "/blocks/latest", nil) - // require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + // res, body := request(t, "GET", "/blocks/latest", nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) // var m ctypes.ResultBlock // decoder := json.NewDecoder(res.Body) @@ -167,8 +156,8 @@ func TestBlock(t *testing.T) { // -- - res := request(t, r, "GET", "/blocks/1", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body := request(t, "GET", "/blocks/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) var m ctypes.ResultBlock decoder := json.NewDecoder(res.Body) @@ -179,19 +168,18 @@ func TestBlock(t *testing.T) { // -- - res = request(t, r, "GET", "/blocks/2", nil) - require.Equal(t, http.StatusNotFound, res.Code, res.Body.String()) + res, body = request(t, "GET", "/blocks/2", nil) + require.Equal(t, http.StatusNotFound, res.StatusCode, body) } func TestValidators(t *testing.T) { - _, _ = startServer(t) - // TODO need to kill server after - prepareClient(t) - cdc := app.MakeCodec() - r := initRouter(cdc) + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) - // res := request(t, r, "GET", "/validatorsets/latest", nil) - // require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + // res, body := request(t, "GET", "/validatorsets/latest", nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) // var m ctypes.ResultValidators // decoder := json.NewDecoder(res.Body) @@ -202,8 +190,8 @@ func TestValidators(t *testing.T) { // -- - res := request(t, r, "GET", "/validatorsets/1", nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body := request(t, "GET", "/validatorsets/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) var m ctypes.ResultValidators decoder := json.NewDecoder(res.Body) @@ -214,17 +202,15 @@ func TestValidators(t *testing.T) { // -- - res = request(t, r, "GET", "/validatorsets/2", nil) - require.Equal(t, http.StatusNotFound, res.Code) + res, body = request(t, "GET", "/validatorsets/2", nil) + require.Equal(t, http.StatusNotFound, res.StatusCode) } func TestCoinSend(t *testing.T) { - ch := server.StartServer(t) - defer close(ch) - - prepareClient(t) - cdc := app.MakeCodec() - r := initRouter(cdc) + err := tests.InitServer() + require.Nil(t, err) + err := tests.StartServer() + require.Nil(t, err) // TODO make that account has coins kb := client.MockKeyBase() @@ -233,12 +219,12 @@ func TestCoinSend(t *testing.T) { addr := string(info.Address()) // query empty - res := request(t, r, "GET", "/accounts/1234567890123456789012345678901234567890", nil) - require.Equal(t, http.StatusNoContent, res.Code, res.Body.String()) + res, body := request(t, "GET", "/accounts/1234567890123456789012345678901234567890", nil) + require.Equal(t, http.StatusNoContent, res.StatusCode, body) // query - res = request(t, r, "GET", "/accounts/"+addr, nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "GET", "/accounts/"+addr, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, `{ "coins": [ @@ -247,12 +233,12 @@ func TestCoinSend(t *testing.T) { "amount": 9007199254740992 } ] - }`, res.Body.String()) + }`, body) // create account to send in keybase var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res = request(t, r, "POST", "/keys", jsonStr) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "POST", "/keys", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) // create receive address receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) @@ -268,12 +254,12 @@ func TestCoinSend(t *testing.T) { "amount": 1 }] }`) - res = request(t, r, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) // check if received - res = request(t, r, "GET", "/accounts/"+receiveAddr, nil) - require.Equal(t, http.StatusOK, res.Code, res.Body.String()) + res, body = request(t, "GET", "/accounts/"+receiveAddr, nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, `{ "coins": [ @@ -282,7 +268,7 @@ func TestCoinSend(t *testing.T) { "amount": 1 } ] - }`, res.Body.String()) + }`, body) } //__________________________________________________________ @@ -301,86 +287,21 @@ func prepareClient(t *testing.T) { app.Commit() } -// 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! +func request(t *testing.T, method string, path string, payload []byte) (*http.Response, string) { + var res *http.Response + var err error + if method == "GET" { + res, err = http.Get(path) } - viper.Set("home", rootDir) - return func() { - os.RemoveAll(rootDir) + if method == "POST" { + res, err = http.Post(path, "application/json", bytes.NewBuffer(payload)) } -} - -// from baseoind.main -func defaultOptions(addr string) func(args []string) (json.RawMessage, error) { - return func(args []string) (json.RawMessage, error) { - opts := fmt.Sprintf(`{ - "accounts": [{ - "address": "%s", - "coins": [ - { - "denom": "mycoin", - "amount": 9007199254740992 - } - ] - }] - }`, addr) - return json.RawMessage(opts), nil - } -} - -func startServer(t *testing.T) (types.Address, string) { - defer setupViper()() - // init server - addr, secret, err := server.GenerateCoinKey() - require.NoError(t, err) - initCmd := server.InitCmd(defaultOptions(addr.String()), log.NewNopLogger()) - err = initCmd.RunE(nil, nil) require.NoError(t, err) - // start server - viper.Set("with-tendermint", true) - startCmd := server.StartCmd(mock.NewApp, log.NewNopLogger()) - timeout := time.Duration(3) * time.Second - - err = runOrTimeout(startCmd, timeout) + output, err := ioutil.ReadAll(res.Body) require.NoError(t, err) - return addr, secret -} - -// copied from server/start_test.go -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 - } -} - -func request(t *testing.T, r http.Handler, method string, path string, payload []byte) *httptest.ResponseRecorder { - - req, err := http.NewRequest(method, path, bytes.NewBuffer(payload)) - require.Nil(t, err) - res := httptest.NewRecorder() - - r.ServeHTTP(res, req) - return res + return res, string(output) } func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { diff --git a/tests/basecli_test.go b/tests/tests.go similarity index 100% rename from tests/basecli_test.go rename to tests/tests.go From c8032a3588531292f4526f281e08b867e8e557af Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 12 Mar 2018 13:50:49 +0100 Subject: [PATCH 46/64] ... --- tests/tests.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.go b/tests/tests.go index 8baebfae36..d1833c108f 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -1,4 +1,4 @@ -package main +package tests import ( "bufio" From fdb9d5f580a874574e95d8aa556dfb555c31bc84 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 12 Mar 2018 15:47:26 +0100 Subject: [PATCH 47/64] moved actual go commands --- client/lcd/lcd_test.go | 43 ++++++++++++++++++------------------------ tests/tests.go | 21 ++++++++++++++++----- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b9b4d9993d..54cd7bf090 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "os" + "os/exec" "regexp" "testing" @@ -28,10 +29,8 @@ import ( ) func TestKeys(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // empty keys res, body := request(t, "GET", "/keys", nil) @@ -94,15 +93,17 @@ func TestKeys(t *testing.T) { jsonStr = []byte(`{"password":"12345678901"}`) res, body = request(t, "DELETE", "/keys/test", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) +} - db.Close() +//XXX +func junkInit(t *testing.T) *exec.Cmd { + tests.TestInitBasecoin(t) + return tests.StartServerForTest(t) } func TestVersion(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // node info res, body := request(t, "GET", "/version", nil) @@ -115,10 +116,8 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // node info res, body := request(t, "GET", "/node_info", nil) @@ -139,10 +138,8 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // res, body := request(t, "GET", "/blocks/latest", nil) // require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -173,10 +170,8 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // res, body := request(t, "GET", "/validatorsets/latest", nil) // require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -207,10 +202,8 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - err := tests.InitServer() - require.Nil(t, err) - err := tests.StartServer() - require.Nil(t, err) + cmd := junkInit(t) + defer cmd.Process.Kill() // TODO make that account has coins kb := client.MockKeyBase() diff --git a/tests/tests.go b/tests/tests.go index d1833c108f..631f558623 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -9,6 +9,8 @@ import ( //"strings" "testing" "time" + + "github.com/stretchr/testify/require" ) // Tests assume the `basecoind` and `basecli` binaries @@ -16,6 +18,7 @@ import ( // TODO remove test dirs if tests are successful +//nolint var ( basecoind = "build/basecoind" basecli = "build/basecli" @@ -42,7 +45,8 @@ func whereIsBasecli() string { return filepath.Join(gopath(), basecli) } -func TestInitBaseCoin(t *testing.T) { +// Init Basecoin Test +func TestInitBasecoin(t *testing.T) { Clean() var err error @@ -101,10 +105,6 @@ func makeKeys() error { return nil } -// these are in the original bash tests -func TestBaseCliRecover(t *testing.T) {} -func TestBaseCliShow(t *testing.T) {} - func _TestSendCoins(t *testing.T) { if err := StartServer(); err != nil { t.Error(err) @@ -163,6 +163,17 @@ func StartServer() error { // see: https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang } +// expects TestInitBaseCoin to have been run +func StartServerForTest(t *testing.T) *exec.Cmd { + cmdName := whereIsBasecoind() + cmdArgs := []string{"start", "--home", basecoindDir} + cmd := exec.Command(cmdName, cmdArgs...) + err := cmd.Start() + require.Nil(t, err) + return cmd +} + +// clean the directories func Clean() { // ignore errors b/c the dirs may not yet exist os.Remove(basecoindDir) From 1778a27082d62cdfe9351a49438da51337cbae54 Mon Sep 17 00:00:00 2001 From: rigelrozanski Date: Mon, 12 Mar 2018 16:31:27 +0100 Subject: [PATCH 48/64] more of the go-bash stuff for REST --- client/lcd/lcd_test.go | 98 ++++++++++++++++++++++-------------------- server/start_test.go | 4 +- server/test_helpers.go | 5 ++- tests/tests.go | 48 ++++++++++++++++++++- 4 files changed, 104 insertions(+), 51 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 54cd7bf090..cbeeb0bd0c 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -7,7 +7,6 @@ import ( "io/ioutil" "net/http" "os" - "os/exec" "regexp" "testing" @@ -29,16 +28,16 @@ import ( ) func TestKeys(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() // empty keys - res, body := request(t, "GET", "/keys", nil) + res, body := request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, body, "[]", "Expected an empty array") // get seed - res, body = request(t, "GET", "/keys/seed", nil) + res, body = request(t, port, "GET", "/keys/seed", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) seed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) @@ -48,30 +47,30 @@ func TestKeys(t *testing.T) { // add key var jsonStr = []byte(`{"name":"test_fail", "password":"1234567890"}`) - res, body = request(t, "POST", "/keys", jsonStr) + res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res, body = request(t, "POST", "/keys", jsonStr) + res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusOK, res.StatusCode, body) addr := body assert.Len(t, addr, 40, "Returned address has wrong format", addr) // existing keys - res, body = request(t, "GET", "/keys", nil) + res, body = request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m [1]keys.KeyOutput decoder := json.NewDecoder(res.Body) err = decoder.Decode(&m) - require.NoError(t, err) + require.Nil(t, err) assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") // select key - res, body = request(t, "GET", "/keys/test", nil) + res, body = request(t, port, "GET", "/keys/test", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput decoder = json.NewDecoder(res.Body) @@ -82,31 +81,37 @@ func TestKeys(t *testing.T) { // update key jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - res, body = request(t, "PUT", "/keys/test", jsonStr) + res, body = request(t, port, "PUT", "/keys/test", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // here it should say unauthorized as we changed the password before - res, body = request(t, "PUT", "/keys/test", jsonStr) + res, body = request(t, port, "PUT", "/keys/test", jsonStr) require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - res, body = request(t, "DELETE", "/keys/test", jsonStr) + res, body = request(t, port, "DELETE", "/keys/test", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) } //XXX -func junkInit(t *testing.T) *exec.Cmd { +func junkInit(t *testing.T) (kill func(), port string) { tests.TestInitBasecoin(t) - return tests.StartServerForTest(t) + cmdStart := tests.StartNodeServerForTest(t) + cmdLCD, port := tests.StartLCDServerForTest(t) + kill = func() { + cmdLCD.Process.Kill() + cmdStart.Process.Kill() + } + return kill, port } func TestVersion(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() // node info - res, body := request(t, "GET", "/version", nil) + res, body := request(t, port, "GET", "/version", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) reg, err := regexp.Compile(`\d+\.\d+\.\d+(-dev)?`) @@ -116,11 +121,11 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() // node info - res, body := request(t, "GET", "/node_info", nil) + res, body := request(t, port, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m p2p.NodeInfo @@ -131,17 +136,17 @@ func TestNodeStatus(t *testing.T) { assert.NotEqual(t, p2p.NodeInfo{}, m, "res: %v", res) // syncing - res, body = request(t, "GET", "/syncing", nil) + res, body = request(t, port, "GET", "/syncing", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, "true", body) } func TestBlock(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() - // res, body := request(t, "GET", "/blocks/latest", nil) + // res, body := request(t, port, "GET", "/blocks/latest", nil) // require.Equal(t, http.StatusOK, res.StatusCode, body) // var m ctypes.ResultBlock @@ -153,7 +158,7 @@ func TestBlock(t *testing.T) { // -- - res, body := request(t, "GET", "/blocks/1", nil) + res, body := request(t, port, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m ctypes.ResultBlock @@ -165,15 +170,15 @@ func TestBlock(t *testing.T) { // -- - res, body = request(t, "GET", "/blocks/2", nil) + res, body = request(t, port, "GET", "/blocks/2", nil) require.Equal(t, http.StatusNotFound, res.StatusCode, body) } func TestValidators(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() - // res, body := request(t, "GET", "/validatorsets/latest", nil) + // res, body := request(t, port, "GET", "/validatorsets/latest", nil) // require.Equal(t, http.StatusOK, res.StatusCode, body) // var m ctypes.ResultValidators @@ -185,7 +190,7 @@ func TestValidators(t *testing.T) { // -- - res, body := request(t, "GET", "/validatorsets/1", nil) + res, body := request(t, port, "GET", "/validatorsets/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m ctypes.ResultValidators @@ -197,26 +202,26 @@ func TestValidators(t *testing.T) { // -- - res, body = request(t, "GET", "/validatorsets/2", nil) + res, body = request(t, port, "GET", "/validatorsets/2", nil) require.Equal(t, http.StatusNotFound, res.StatusCode) } func TestCoinSend(t *testing.T) { - cmd := junkInit(t) - defer cmd.Process.Kill() + kill, port := junkInit(t) + defer kill() // TODO make that account has coins kb := client.MockKeyBase() info, seed, err := kb.Create("account_with_coins", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - require.NoError(t, err) + require.Nil(t, err) addr := string(info.Address()) // query empty - res, body := request(t, "GET", "/accounts/1234567890123456789012345678901234567890", nil) + res, body := request(t, port, "GET", "/accounts/1234567890123456789012345678901234567890", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) // query - res, body = request(t, "GET", "/accounts/"+addr, nil) + res, body = request(t, port, "GET", "/accounts/"+addr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, `{ @@ -230,12 +235,12 @@ func TestCoinSend(t *testing.T) { // create account to send in keybase var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res, body = request(t, "POST", "/keys", jsonStr) + res, body = request(t, port, "POST", "/keys", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // create receive address receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - require.NoError(t, err) + require.Nil(t, err) receiveAddr := string(receiveInfo.Address()) // send @@ -247,11 +252,11 @@ func TestCoinSend(t *testing.T) { "amount": 1 }] }`) - res, body = request(t, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // check if received - res, body = request(t, "GET", "/accounts/"+receiveAddr, nil) + res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) assert.Equal(t, `{ @@ -280,19 +285,20 @@ func prepareClient(t *testing.T) { app.Commit() } -func request(t *testing.T, method string, path string, payload []byte) (*http.Response, string) { +func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { var res *http.Response var err error + url := fmt.Sprintf("http://localhost:%v%v", port, path) if method == "GET" { - res, err = http.Get(path) + res, err = http.Get(url) } if method == "POST" { - res, err = http.Post(path, "application/json", bytes.NewBuffer(payload)) + res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) } - require.NoError(t, err) + require.Nil(t, err) output, err := ioutil.ReadAll(res.Body) - require.NoError(t, err) + require.Nil(t, err) return res, string(output) } diff --git a/server/start_test.go b/server/start_test.go index 5bb2eccbbc..de396c46ff 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -24,7 +24,7 @@ func TestStartStandAlone(t *testing.T) { viper.Set(flagWithTendermint, false) viper.Set(flagAddress, "localhost:11122") startCmd := StartCmd(mock.NewApp, logger) - startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address + startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second ch := RunOrTimeout(startCmd, timeout, t) @@ -44,7 +44,7 @@ func TestStartWithTendermint(t *testing.T) { // set up app and start up viper.Set(flagWithTendermint, true) startCmd := StartCmd(mock.NewApp, logger) - startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address + startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second //a, _ := startCmd.Flags().GetString(flagAddress) diff --git a/server/test_helpers.go b/server/test_helpers.go index 279583f060..e490da3773 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -17,7 +17,8 @@ import ( ) // Get a free address for a test tendermint server -func FreeAddr(t *testing.T) string { +// protocol is either tcp, http, etc +func FreeTCPAddr(t *testing.T) string { l, err := net.Listen("tcp", "0.0.0.0:0") defer l.Close() require.Nil(t, err) @@ -51,7 +52,7 @@ func StartServer(t *testing.T) chan error { // start server viper.Set(flagWithTendermint, true) startCmd := StartCmd(mock.NewApp, log.NewNopLogger()) - startCmd.Flags().Set(flagAddress, FreeAddr(t)) // set to a new free address + startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second return RunOrTimeout(startCmd, timeout, t) diff --git a/tests/tests.go b/tests/tests.go index 631f558623..1719622453 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -6,10 +6,12 @@ import ( "os" "os/exec" "path/filepath" + "strings" //"strings" "testing" "time" + "github.com/cosmos/cosmos-sdk/server" "github.com/stretchr/testify/require" ) @@ -163,8 +165,35 @@ func StartServer() error { // see: https://stackoverflow.com/questions/11886531/terminating-a-process-started-with-os-exec-in-golang } +// Init Basecoin Test +func InitServerForTest(t *testing.T) { + Clean() + + var err error + + password := "some-random-password" + usePassword := exec.Command("echo", password) + + initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir) + + initBasecoind.Stdin, err = usePassword.StdoutPipe() + require.Nil(t, err) + + initBasecoind.Stdout = os.Stdout + + err = initBasecoind.Start() + require.Nil(t, err) + err = usePassword.Run() + require.Nil(t, err) + err = initBasecoind.Wait() + require.Nil(t, err) + + err = makeKeys() + require.Nil(t, err) +} + // expects TestInitBaseCoin to have been run -func StartServerForTest(t *testing.T) *exec.Cmd { +func StartNodeServerForTest(t *testing.T) *exec.Cmd { cmdName := whereIsBasecoind() cmdArgs := []string{"start", "--home", basecoindDir} cmd := exec.Command(cmdName, cmdArgs...) @@ -173,6 +202,23 @@ func StartServerForTest(t *testing.T) *exec.Cmd { return cmd } +// expects TestInitBaseCoin to have been run +func StartLCDServerForTest(t *testing.T) (cmd *exec.Cmd, port string) { + cmdName := whereIsBasecli() + port = strings.Split(server.FreeTCPAddr(t), ":")[2] + cmdArgs := []string{ + "rest-server", + "--home", + basecoindDir, + "--port", + port, + } + cmd = exec.Command(cmdName, cmdArgs...) + err := cmd.Start() + require.Nil(t, err) + return cmd, port +} + // clean the directories func Clean() { // ignore errors b/c the dirs may not yet exist From 39c8e4eb7f04841d07b2a501c61c1e9ae86588c3 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 13 Mar 2018 13:09:28 +0100 Subject: [PATCH 49/64] client/lcd: fix up some tests. print statements abound --- client/lcd/lcd_test.go | 51 +++++++++++++++++++++--------------------- tests/tests.go | 11 +++++++-- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index cbeeb0bd0c..b4ddeb2222 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -34,7 +34,7 @@ func TestKeys(t *testing.T) { // empty keys res, body := request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, body, "[]", "Expected an empty array") + assert.Equal(t, "[]", body, "Expected an empty array") // get seed res, body = request(t, port, "GET", "/keys/seed", nil) @@ -62,8 +62,8 @@ func TestKeys(t *testing.T) { res, body = request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m [1]keys.KeyOutput - decoder := json.NewDecoder(res.Body) - err = decoder.Decode(&m) + fmt.Println("BUF", body) + err = json.Unmarshal([]byte(body), &m) require.Nil(t, err) assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") @@ -73,11 +73,12 @@ func TestKeys(t *testing.T) { res, body = request(t, port, "GET", "/keys/test", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput - decoder = json.NewDecoder(res.Body) - err = decoder.Decode(&m2) + fmt.Println("BUF", body) + err = json.Unmarshal([]byte(body), &m2) + require.Nil(t, err) - assert.Equal(t, m2.Name, "test", "Did not serve keys name correctly") - assert.Equal(t, m2.Address, addr, "Did not serve keys Address correctly") + assert.Equal(t, "test", m2.Name, "Did not serve keys name correctly") + assert.Equal(t, addr, m2.Address, "Did not serve keys Address correctly") // update key jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) @@ -94,7 +95,7 @@ func TestKeys(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode, body) } -//XXX +// TODO/XXX: We should be spawning what we need in process, not shelling out func junkInit(t *testing.T) (kill func(), port string) { tests.TestInitBasecoin(t) cmdStart := tests.StartNodeServerForTest(t) @@ -128,12 +129,11 @@ func TestNodeStatus(t *testing.T) { res, body := request(t, port, "GET", "/node_info", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m p2p.NodeInfo - decoder := json.NewDecoder(res.Body) - err := decoder.Decode(&m) + var nodeInfo p2p.NodeInfo + err := json.Unmarshal([]byte(body), &nodeInfo) require.Nil(t, err, "Couldn't parse node info") - assert.NotEqual(t, p2p.NodeInfo{}, m, "res: %v", res) + assert.NotEqual(t, p2p.NodeInfo{}, nodeInfo, "res: %v", res) // syncing res, body = request(t, port, "GET", "/syncing", nil) @@ -161,12 +161,11 @@ func TestBlock(t *testing.T) { res, body := request(t, port, "GET", "/blocks/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m ctypes.ResultBlock - decoder := json.NewDecoder(res.Body) - err := decoder.Decode(&m) + var resultBlock ctypes.ResultBlock + err := json.Unmarshal([]byte(body), &resultBlock) require.Nil(t, err, "Couldn't parse block") - assert.NotEqual(t, ctypes.ResultBlock{}, m) + assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock) // -- @@ -193,12 +192,11 @@ func TestValidators(t *testing.T) { res, body := request(t, port, "GET", "/validatorsets/1", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m ctypes.ResultValidators - decoder := json.NewDecoder(res.Body) - err := decoder.Decode(&m) + var resultVals ctypes.ResultValidators + err := json.Unmarshal([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") - assert.NotEqual(t, ctypes.ResultValidators{}, m) + assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) // -- @@ -289,12 +287,13 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res var res *http.Response var err error url := fmt.Sprintf("http://localhost:%v%v", port, path) - if method == "GET" { - res, err = http.Get(url) - } - if method == "POST" { - res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) - } + fmt.Println("URL", url) + req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) + require.Nil(t, err) + res, err = http.DefaultClient.Do(req) + // res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) + fmt.Println("METHOD", method) + fmt.Println("RES", res) require.Nil(t, err) output, err := ioutil.ReadAll(res.Body) diff --git a/tests/tests.go b/tests/tests.go index 1719622453..4e5940c6ad 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -210,12 +210,19 @@ func StartLCDServerForTest(t *testing.T) (cmd *exec.Cmd, port string) { "rest-server", "--home", basecoindDir, - "--port", - port, + "--bind", + fmt.Sprintf("localhost:%s", port), } + fmt.Println("----------------------------") cmd = exec.Command(cmdName, cmdArgs...) + fmt.Println("CMD", cmd) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr err := cmd.Start() require.Nil(t, err) + fmt.Println("PORT", port) + fmt.Println("----------------------------") + time.Sleep(time.Second * 2) // TODO: LOL return cmd, port } From b44690563a549355bce450b3018f28c6907eae79 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 13 Mar 2018 15:43:36 +0100 Subject: [PATCH 50/64] hack city --- tests/tests.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/tests.go b/tests/tests.go index 4e5940c6ad..f153f60340 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -2,6 +2,7 @@ package tests import ( "bufio" + "bytes" "fmt" "os" "os/exec" @@ -63,7 +64,8 @@ func TestInitBasecoin(t *testing.T) { t.Error(err) } - initBasecoind.Stdout = os.Stdout + buf := new(bytes.Buffer) + initBasecoind.Stdout = buf if err := initBasecoind.Start(); err != nil { t.Error(err) @@ -78,6 +80,13 @@ func TestInitBasecoin(t *testing.T) { if err := makeKeys(); err != nil { t.Error(err) } + + fmt.Println("-----------------") + theOutput := strings.Split(buf.String(), "\n") + for i, o := range theOutput { + fmt.Println(i, o) + } + fmt.Println("-----------------") } func makeKeys() error { From 579bd5612783c77b55c301b9ac883b8d9a8665ae Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Tue, 13 Mar 2018 16:42:29 +0100 Subject: [PATCH 51/64] fixed some tests --- client/lcd/.gitignore | 2 +- client/lcd/lcd_test.go | 72 +++++++++++++++++++--------------------- client/rpc/block.go | 12 +++---- client/rpc/root.go | 4 +-- client/rpc/validators.go | 12 +++---- tests/tests.go | 45 ++++++++++++------------- x/auth/rest/query.go | 2 +- 7 files changed, 73 insertions(+), 76 deletions(-) diff --git a/client/lcd/.gitignore b/client/lcd/.gitignore index 5225ad9d69..ae90670254 100644 --- a/client/lcd/.gitignore +++ b/client/lcd/.gitignore @@ -1 +1 @@ -keys.db \ No newline at end of file +tmp-base* \ No newline at end of file diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index b4ddeb2222..d24dbd44ba 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -28,7 +28,7 @@ import ( ) func TestKeys(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() // empty keys @@ -96,19 +96,19 @@ func TestKeys(t *testing.T) { } // TODO/XXX: We should be spawning what we need in process, not shelling out -func junkInit(t *testing.T) (kill func(), port string) { - tests.TestInitBasecoin(t) +func junkInit(t *testing.T) (kill func(), port string, seed string) { + seed = tests.TestInitBasecoin(t) cmdStart := tests.StartNodeServerForTest(t) cmdLCD, port := tests.StartLCDServerForTest(t) kill = func() { cmdLCD.Process.Kill() cmdStart.Process.Kill() } - return kill, port + return kill, port, seed } func TestVersion(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() // node info @@ -122,7 +122,7 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() // node info @@ -143,25 +143,14 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() - // res, body := request(t, port, "GET", "/blocks/latest", nil) - // require.Equal(t, http.StatusOK, res.StatusCode, body) + var resultBlock ctypes.ResultBlock - // var m ctypes.ResultBlock - // decoder := json.NewDecoder(res.Body) - // err := decoder.Decode(&m) - // require.Nil(t, err, "Couldn't parse block") - - // assert.NotEqual(t, ctypes.ResultBlock{}, m) - - // -- - - res, body := request(t, port, "GET", "/blocks/1", nil) + res, body := request(t, port, "GET", "/blocks/latest", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var resultBlock ctypes.ResultBlock err := json.Unmarshal([]byte(body), &resultBlock) require.Nil(t, err, "Couldn't parse block") @@ -169,30 +158,29 @@ func TestBlock(t *testing.T) { // -- - res, body = request(t, port, "GET", "/blocks/2", nil) + res, body = request(t, port, "GET", "/blocks/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = json.Unmarshal([]byte(body), &resultBlock) + require.Nil(t, err, "Couldn't parse block") + + assert.NotEqual(t, ctypes.ResultBlock{}, resultBlock) + + // -- + + res, body = request(t, port, "GET", "/blocks/1000000000", nil) require.Equal(t, http.StatusNotFound, res.StatusCode, body) } func TestValidators(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() - // res, body := request(t, port, "GET", "/validatorsets/latest", nil) - // require.Equal(t, http.StatusOK, res.StatusCode, body) + var resultVals ctypes.ResultValidators - // var m ctypes.ResultValidators - // decoder := json.NewDecoder(res.Body) - // err := decoder.Decode(&m) - // require.Nil(t, err, "Couldn't parse validatorset") - - // assert.NotEqual(t, ctypes.ResultValidators{}, m) - - // -- - - res, body := request(t, port, "GET", "/validatorsets/1", nil) + res, body := request(t, port, "GET", "/validatorsets/latest", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var resultVals ctypes.ResultValidators err := json.Unmarshal([]byte(body), &resultVals) require.Nil(t, err, "Couldn't parse validatorset") @@ -200,12 +188,22 @@ func TestValidators(t *testing.T) { // -- - res, body = request(t, port, "GET", "/validatorsets/2", nil) + res, body = request(t, port, "GET", "/validatorsets/1", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + err = json.Unmarshal([]byte(body), &resultVals) + require.Nil(t, err, "Couldn't parse validatorset") + + assert.NotEqual(t, ctypes.ResultValidators{}, resultVals) + + // -- + + res, body = request(t, port, "GET", "/validatorsets/1000000000", nil) require.Equal(t, http.StatusNotFound, res.StatusCode) } func TestCoinSend(t *testing.T) { - kill, port := junkInit(t) + kill, port, _ := junkInit(t) defer kill() // TODO make that account has coins diff --git a/client/rpc/block.go b/client/rpc/block.go index 12c18aa4c2..7f197051a9 100644 --- a/client/rpc/block.go +++ b/client/rpc/block.go @@ -101,12 +101,12 @@ func BlockRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/block/{height}'.")) return } - // chainHeight, err := GetChainHeight() - // if height > chainHeight { - // w.WriteHeader(404) - // w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) - // return - // } + chainHeight, err := GetChainHeight() + if height > chainHeight { + w.WriteHeader(404) + w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + return + } output, err := getBlock(&height) if err != nil { w.WriteHeader(500) diff --git a/client/rpc/root.go b/client/rpc/root.go index 03c964070e..8b04c044f4 100644 --- a/client/rpc/root.go +++ b/client/rpc/root.go @@ -47,8 +47,8 @@ func initClientCommand() *cobra.Command { func RegisterRoutes(r *mux.Router) { r.HandleFunc("/node_info", NodeInfoRequestHandler).Methods("GET") r.HandleFunc("/syncing", NodeSyncingRequestHandler).Methods("GET") - // r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET") + r.HandleFunc("/blocks/latest", LatestBlockRequestHandler).Methods("GET") r.HandleFunc("/blocks/{height}", BlockRequestHandler).Methods("GET") - // r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET") + r.HandleFunc("/validatorsets/latest", LatestValidatorsetRequestHandler).Methods("GET") r.HandleFunc("/validatorsets/{height}", ValidatorsetRequestHandler).Methods("GET") } diff --git a/client/rpc/validators.go b/client/rpc/validators.go index c7beeaa4e8..32c7680ec6 100644 --- a/client/rpc/validators.go +++ b/client/rpc/validators.go @@ -78,12 +78,12 @@ func ValidatorsetRequestHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("ERROR: Couldn't parse block height. Assumed format is '/validatorsets/{height}'.")) return } - // chainHeight, err := GetChainHeight() - // if height > chainHeight { - // w.WriteHeader(404) - // w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) - // return - // } + chainHeight, err := GetChainHeight() + if height > chainHeight { + w.WriteHeader(404) + w.Write([]byte("ERROR: Requested block height is bigger then the chain length.")) + return + } output, err := getValidators(&height) if err != nil { w.WriteHeader(500) diff --git a/tests/tests.go b/tests/tests.go index f153f60340..3705743c12 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -49,20 +49,16 @@ func whereIsBasecli() string { } // Init Basecoin Test -func TestInitBasecoin(t *testing.T) { +func TestInitBasecoin(t *testing.T) string { Clean() var err error password := "some-random-password" - usePassword := exec.Command("echo", password) initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir) - - initBasecoind.Stdin, err = usePassword.StdoutPipe() - if err != nil { - t.Error(err) - } + cmdWriter, err := initBasecoind.StdinPipe() + require.Nil(t, err) buf := new(bytes.Buffer) initBasecoind.Stdout = buf @@ -70,32 +66,32 @@ func TestInitBasecoin(t *testing.T) { if err := initBasecoind.Start(); err != nil { t.Error(err) } - if err := usePassword.Run(); err != nil { - t.Error(err) - } + + _, err = cmdWriter.Write([]byte(password)) + require.Nil(t, err) + cmdWriter.Close() + if err := initBasecoind.Wait(); err != nil { t.Error(err) } - if err := makeKeys(); err != nil { - t.Error(err) + // get seed from initialization + theOutput := strings.Split(buf.String(), "\n") + var seedLine int + for seedLine, o := range theOutput { + if strings.HasPrefix(string(o), "Secret phrase") { + seedLine++ + break + } } - fmt.Println("-----------------") - theOutput := strings.Split(buf.String(), "\n") - for i, o := range theOutput { - fmt.Println(i, o) - } - fmt.Println("-----------------") + return string(theOutput[seedLine]) } func makeKeys() error { - var err error for _, acc := range ACCOUNTS { - pass := exec.Command("echo", "1234567890") makeKeys := exec.Command(whereIsBasecli(), "keys", "add", acc, "--home", basecliDir) - - makeKeys.Stdin, err = pass.StdoutPipe() + cmdWriter, err := makeKeys.StdinPipe() if err != nil { return err } @@ -104,9 +100,11 @@ func makeKeys() error { if err := makeKeys.Start(); err != nil { return err } - if err := pass.Run(); err != nil { + cmdWriter.Write([]byte("1234567890")) + if err != nil { return err } + cmdWriter.Close() if err := makeKeys.Wait(); err != nil { return err @@ -176,6 +174,7 @@ func StartServer() error { // Init Basecoin Test func InitServerForTest(t *testing.T) { + // TODO cleanup doesn't work -> keys are still there in each iteration Clean() var err error diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go index 1536e692a5..7ce1afc322 100644 --- a/x/auth/rest/query.go +++ b/x/auth/rest/query.go @@ -40,7 +40,7 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.Pa } // the query will return empty if there is no data for this account - if len(res) == 0 { + if res == nil { w.WriteHeader(http.StatusNoContent) return } From 1cd6ec1084eb7d042bd2c0e625b9c1a421004be0 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Wed, 14 Mar 2018 11:43:31 +0100 Subject: [PATCH 52/64] most tests working --- client/keys/add.go | 30 ++++++++++----- client/lcd/lcd_test.go | 85 +++++++++++++++--------------------------- tests/tests.go | 29 ++++++-------- x/auth/rest/query.go | 9 +++-- 4 files changed, 69 insertions(+), 84 deletions(-) diff --git a/client/keys/add.go b/client/keys/add.go index 19d363850f..356c7369c4 100644 --- a/client/keys/add.go +++ b/client/keys/add.go @@ -3,6 +3,7 @@ package keys import ( "encoding/json" "fmt" + "io/ioutil" "net/http" "github.com/cosmos/cosmos-sdk/client" @@ -129,7 +130,7 @@ func printCreate(info keys.Info, seed string) { type NewKeyBody struct { Name string `json:"name"` Password string `json:"password"` - Seed string `json="seed"` + Seed string `json:"seed"` } func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { @@ -143,33 +144,44 @@ func AddNewKeyRequestHandler(w http.ResponseWriter, r *http.Request) { return } - decoder := json.NewDecoder(r.Body) - err = decoder.Decode(&m) + body, err := ioutil.ReadAll(r.Body) + err = json.Unmarshal(body, &m) + if err != nil { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) return } if m.Name == "" { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("You have to specify a name for the locally stored account.")) return } if m.Password == "" { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("You have to specify a password for the locally stored account.")) return } if m.Seed == "" { - w.WriteHeader(400) + w.WriteHeader(http.StatusBadRequest) w.Write([]byte("You have to specify a seed for the locally stored account.")) return } + // check if already exists + infos, err := kb.List() + for _, i := range infos { + if i.Name == m.Name { + w.WriteHeader(http.StatusConflict) + w.Write([]byte(fmt.Sprintf("Account with name %s already exists.", m.Name))) + return + } + } + + // create account info, err := kb.Recover(m.Name, m.Password, m.Seed) - // TODO handle different errors if err != nil { - w.WriteHeader(500) + w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index d24dbd44ba..6fda0bc4c7 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -9,22 +9,17 @@ import ( "os" "regexp" "testing" + "time" - "github.com/spf13/viper" - - "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - abci "github.com/tendermint/abci/types" cryptoKeys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" - dbm "github.com/tendermint/tmlibs/db" - "github.com/tendermint/tmlibs/log" ) func TestKeys(t *testing.T) { @@ -62,7 +57,6 @@ func TestKeys(t *testing.T) { res, body = request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m [1]keys.KeyOutput - fmt.Println("BUF", body) err = json.Unmarshal([]byte(body), &m) require.Nil(t, err) @@ -73,7 +67,6 @@ func TestKeys(t *testing.T) { res, body = request(t, port, "GET", "/keys/test", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput - fmt.Println("BUF", body) err = json.Unmarshal([]byte(body), &m2) require.Nil(t, err) @@ -95,18 +88,6 @@ func TestKeys(t *testing.T) { require.Equal(t, http.StatusOK, res.StatusCode, body) } -// TODO/XXX: We should be spawning what we need in process, not shelling out -func junkInit(t *testing.T) (kill func(), port string, seed string) { - seed = tests.TestInitBasecoin(t) - cmdStart := tests.StartNodeServerForTest(t) - cmdLCD, port := tests.StartLCDServerForTest(t) - kill = func() { - cmdLCD.Process.Kill() - cmdStart.Process.Kill() - } - return kill, port, seed -} - func TestVersion(t *testing.T) { kill, port, _ := junkInit(t) defer kill() @@ -146,6 +127,8 @@ func TestBlock(t *testing.T) { kill, port, _ := junkInit(t) defer kill() + time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks + var resultBlock ctypes.ResultBlock res, body := request(t, port, "GET", "/blocks/latest", nil) @@ -176,6 +159,8 @@ func TestValidators(t *testing.T) { kill, port, _ := junkInit(t) defer kill() + time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks + var resultVals ctypes.ResultValidators res, body := request(t, port, "GET", "/validatorsets/latest", nil) @@ -203,19 +188,22 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, seed := junkInit(t) defer kill() - // TODO make that account has coins - kb := client.MockKeyBase() - info, seed, err := kb.Create("account_with_coins", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - require.Nil(t, err) - addr := string(info.Address()) + time.Sleep(time.Second * 2) // TO // query empty - res, body := request(t, port, "GET", "/accounts/1234567890123456789012345678901234567890", nil) + res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) + // create account from seed who has keys + var jsonStr = []byte(fmt.Sprintf(`{"name":"account_with_coins", "password":"1234567890", "seed": "%s"}`, seed)) + res, body = request(t, port, "POST", "/keys", jsonStr) + + assert.Equal(t, http.StatusOK, res.StatusCode, body) + addr := body + // query res, body = request(t, port, "GET", "/accounts/"+addr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -229,19 +217,15 @@ func TestCoinSend(t *testing.T) { ] }`, body) - // create account to send in keybase - var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res, body = request(t, port, "POST", "/keys", jsonStr) - require.Equal(t, http.StatusOK, res.StatusCode, body) - // create receive address + kb := client.MockKeyBase() receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) require.Nil(t, err) receiveAddr := string(receiveInfo.Address()) // send jsonStr = []byte(`{ - "name":"test", + "name":"account_with_coins", "password":"1234567890", "amount":[{ "denom": "mycoin", @@ -268,30 +252,30 @@ func TestCoinSend(t *testing.T) { //__________________________________________________________ // helpers -func defaultLogger() log.Logger { - return log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") -} +// TODO/XXX: We should be spawning what we need in process, not shelling out +func junkInit(t *testing.T) (kill func(), port string, seed string) { + dir, err := ioutil.TempDir("", "tmp-basecoin-") + require.Nil(t, err) -func prepareClient(t *testing.T) { - db := dbm.NewMemDB() - app := baseapp.NewBaseApp(t.Name(), defaultLogger(), db) - viper.Set(client.FlagNode, "localhost:46657") - header := abci.Header{Height: 1} - app.BeginBlock(abci.RequestBeginBlock{Header: header}) - app.Commit() + seed = tests.TestInitBasecoin(t, dir) + cmdStart := tests.StartNodeServerForTest(t, dir) + cmdLCD, port := tests.StartLCDServerForTest(t, dir) + kill = func() { + cmdLCD.Process.Kill() + cmdStart.Process.Kill() + os.Remove(dir) + } + return kill, port, seed } func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { var res *http.Response var err error url := fmt.Sprintf("http://localhost:%v%v", port, path) - fmt.Println("URL", url) req, err := http.NewRequest(method, url, bytes.NewBuffer(payload)) require.Nil(t, err) res, err = http.DefaultClient.Do(req) // res, err = http.Post(url, "application/json", bytes.NewBuffer(payload)) - fmt.Println("METHOD", method) - fmt.Println("RES", res) require.Nil(t, err) output, err := ioutil.ReadAll(res.Body) @@ -299,12 +283,3 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res return res, string(output) } - -func initKeybase(t *testing.T) (cryptoKeys.Keybase, *dbm.GoLevelDB, error) { - os.RemoveAll("./testKeybase") - db, err := dbm.NewGoLevelDB("keys", "./testKeybase") - require.Nil(t, err) - kb := client.GetKeyBase(db) - keys.SetKeyBase(kb) - return kb, db, nil -} diff --git a/tests/tests.go b/tests/tests.go index 3705743c12..cd4aa0bf7e 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -49,14 +49,12 @@ func whereIsBasecli() string { } // Init Basecoin Test -func TestInitBasecoin(t *testing.T) string { - Clean() - +func TestInitBasecoin(t *testing.T, home string) string { var err error password := "some-random-password" - initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", basecoindDir) + initBasecoind := exec.Command(whereIsBasecoind(), "init", "--home", home) cmdWriter, err := initBasecoind.StdinPipe() require.Nil(t, err) @@ -78,9 +76,9 @@ func TestInitBasecoin(t *testing.T) string { // get seed from initialization theOutput := strings.Split(buf.String(), "\n") var seedLine int - for seedLine, o := range theOutput { + for _seedLine, o := range theOutput { if strings.HasPrefix(string(o), "Secret phrase") { - seedLine++ + seedLine = _seedLine + 1 break } } @@ -174,7 +172,6 @@ func StartServer() error { // Init Basecoin Test func InitServerForTest(t *testing.T) { - // TODO cleanup doesn't work -> keys are still there in each iteration Clean() var err error @@ -201,9 +198,9 @@ func InitServerForTest(t *testing.T) { } // expects TestInitBaseCoin to have been run -func StartNodeServerForTest(t *testing.T) *exec.Cmd { +func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { cmdName := whereIsBasecoind() - cmdArgs := []string{"start", "--home", basecoindDir} + cmdArgs := []string{"start", "--home", home} cmd := exec.Command(cmdName, cmdArgs...) err := cmd.Start() require.Nil(t, err) @@ -211,25 +208,21 @@ func StartNodeServerForTest(t *testing.T) *exec.Cmd { } // expects TestInitBaseCoin to have been run -func StartLCDServerForTest(t *testing.T) (cmd *exec.Cmd, port string) { +func StartLCDServerForTest(t *testing.T, home string) (cmd *exec.Cmd, port string) { cmdName := whereIsBasecli() port = strings.Split(server.FreeTCPAddr(t), ":")[2] cmdArgs := []string{ "rest-server", "--home", - basecoindDir, + home, "--bind", fmt.Sprintf("localhost:%s", port), } - fmt.Println("----------------------------") cmd = exec.Command(cmdName, cmdArgs...) - fmt.Println("CMD", cmd) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr err := cmd.Start() require.Nil(t, err) - fmt.Println("PORT", port) - fmt.Println("----------------------------") time.Sleep(time.Second * 2) // TODO: LOL return cmd, port } @@ -237,8 +230,10 @@ func StartLCDServerForTest(t *testing.T) (cmd *exec.Cmd, port string) { // clean the directories func Clean() { // ignore errors b/c the dirs may not yet exist - os.Remove(basecoindDir) - os.Remove(basecliDir) + err := os.Remove(basecoindDir) + panic(err) + err = os.Remove(basecliDir) + panic(err) } /* diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go index 7ce1afc322..dbb53834d8 100644 --- a/x/auth/rest/query.go +++ b/x/auth/rest/query.go @@ -8,7 +8,7 @@ import ( "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" ) @@ -24,6 +24,9 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.Pa return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) addr := vars["address"] + + fmt.Println("ADDR", addr) + bz, err := hex.DecodeString(addr) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -32,7 +35,7 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.Pa } key := sdk.Address(bz) - res, err := client.Query(key, c.storeName) + res, err := builder.Query(key, c.storeName) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(fmt.Sprintf("Could't query account. Error: %s", err.Error()))) @@ -40,7 +43,7 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.Pa } // the query will return empty if there is no data for this account - if res == nil { + if len(res) == 0 { w.WriteHeader(http.StatusNoContent) return } From cf6f04978ce33679ebe55a263d74bd67780e7e9e Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Wed, 14 Mar 2018 13:01:55 +0100 Subject: [PATCH 53/64] adjusted sending + tx tests --- client/builder/builder.go | 13 +--- client/lcd/lcd_test.go | 86 +++++++++++++++++-------- client/tx/root.go | 4 +- client/tx/search.go | 6 ++ examples/basecoin/x/cool/commands/tx.go | 28 +++++++- x/auth/rest/query.go | 2 - x/bank/commands/sendtx.go | 15 ++++- x/bank/rest/sendtx.go | 31 ++++++--- 8 files changed, 131 insertions(+), 54 deletions(-) diff --git a/client/builder/builder.go b/client/builder/builder.go index a64dfeda74..69c95fa3b6 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -88,7 +88,7 @@ func GetFromAddress() (from sdk.Address, err error) { } // sign and build the transaction from the msg -func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { +func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { // build the Sign Messsage from the Standard Message chainID := viper.GetString(client.FlagChainID) @@ -103,16 +103,9 @@ func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { if err != nil { return nil, err } - name := viper.GetString(client.FlagName) // sign and build bz := signMsg.Bytes() - 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 @@ -130,8 +123,8 @@ func SignAndBuild(msg sdk.Msg, cdc *wire.Codec) ([]byte, error) { } // sign and build the transaction from the msg -func SignBuildBroadcast(msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { - txBytes, err := SignAndBuild(msg, cdc) +func SignBuildBroadcast(name string, passphrase string, msg sdk.Msg, cdc *wire.Codec) (*ctypes.ResultBroadcastTxCommit, error) { + txBytes, err := SignAndBuild(name, passphrase, msg, cdc) if err != nil { return nil, err } diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 6fda0bc4c7..e4be056e25 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -197,14 +197,10 @@ func TestCoinSend(t *testing.T) { res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) - // create account from seed who has keys - var jsonStr = []byte(fmt.Sprintf(`{"name":"account_with_coins", "password":"1234567890", "seed": "%s"}`, seed)) - res, body = request(t, port, "POST", "/keys", jsonStr) + // create TX + addr, receiveAddr := doSend(t, port, seed) - assert.Equal(t, http.StatusOK, res.StatusCode, body) - addr := body - - // query + // query sender res, body = request(t, port, "GET", "/accounts/"+addr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -212,30 +208,12 @@ func TestCoinSend(t *testing.T) { "coins": [ { "denom": "mycoin", - "amount": 9007199254740992 + "amount": 9007199254740991 } ] }`, body) - // create receive address - kb := client.MockKeyBase() - receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) - require.Nil(t, err) - receiveAddr := string(receiveInfo.Address()) - - // send - jsonStr = []byte(`{ - "name":"account_with_coins", - "password":"1234567890", - "amount":[{ - "denom": "mycoin", - "amount": 1 - }] - }`) - res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) - require.Equal(t, http.StatusOK, res.StatusCode, body) - - // check if received + // query receiver res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) @@ -249,6 +227,38 @@ func TestCoinSend(t *testing.T) { }`, body) } +func TestTxs(t *testing.T) { + kill, port, seed := junkInit(t) + defer kill() + + // query wrong + res, body := request(t, port, "GET", "/txs", nil) + require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + + // query empty + res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", "8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6"), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + assert.Equal(t, "[]", body) + + // create TX + addr, receiveAddr := doSend(t, port, seed) + + // query sender + res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", addr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + assert.NotEqual(t, "[]", body) + + // query receiver + res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.receiver='%s'", receiveAddr), nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + assert.NotEqual(t, "[]", body) + + // get TX by hash +} + //__________________________________________________________ // helpers @@ -283,3 +293,25 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res return res, string(output) } + +func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr string) { + // create account from seed who has keys + var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + res, body := request(t, port, "POST", "/keys", jsonStr) + + assert.Equal(t, http.StatusOK, res.StatusCode, body) + sendAddr = body + + // create receive address + kb := client.MockKeyBase() + receiveInfo, _, err := kb.Create("receive_address", "1234567890", cryptoKeys.CryptoAlgo("ed25519")) + require.Nil(t, err) + receiveAddr = receiveInfo.PubKey.Address().String() + + // send + jsonStr = []byte(`{ "name":"test", "password":"1234567890", "amount":[{ "denom": "mycoin", "amount": 1 }] }`) + res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) + require.Equal(t, http.StatusOK, res.StatusCode, body) + + return sendAddr, receiveAddr +} diff --git a/client/tx/root.go b/client/tx/root.go index fd8b3f0975..477259e97b 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -24,6 +24,6 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET") r.HandleFunc("/txs/{hash}", QueryTxRequestHandler(cdc)).Methods("GET") - r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") - r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") + // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") + // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") } diff --git a/client/tx/search.go b/client/tx/search.go index 90f89e3e2c..2790750ebe 100644 --- a/client/tx/search.go +++ b/client/tx/search.go @@ -98,6 +98,12 @@ func SearchTxRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Req c := commander{cdc} return func(w http.ResponseWriter, r *http.Request) { tag := r.FormValue("tag") + if tag == "" { + w.WriteHeader(400) + w.Write([]byte("You need to provide a tag to search for.")) + return + } + tags := []string{tag} output, err := c.searchTx(tags) if err != nil { diff --git a/examples/basecoin/x/cool/commands/tx.go b/examples/basecoin/x/cool/commands/tx.go index 2b16546806..f06eb8af42 100644 --- a/examples/basecoin/x/cool/commands/tx.go +++ b/examples/basecoin/x/cool/commands/tx.go @@ -5,7 +5,9 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/wire" @@ -31,8 +33,19 @@ func QuizTxCmd(cdc *wire.Codec) *cobra.Command { // create the message msg := cool.NewQuizMsg(from, args[0]) + // get account name + name := viper.GetString(client.FlagName) + + // get password + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password to sign with '%s':", name) + passphrase, err := client.GetPassword(prompt, buf) + if err != nil { + return err + } + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc) if err != nil { return err } @@ -59,11 +72,22 @@ func SetTrendTxCmd(cdc *wire.Codec) *cobra.Command { return err } + // get account name + name := viper.GetString(client.FlagName) + + // get password + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password to sign with '%s':", name) + passphrase, err := client.GetPassword(prompt, buf) + if err != nil { + return err + } + // create the message msg := cool.NewSetTrendMsg(from, args[0]) // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, cdc) + res, err := builder.SignBuildBroadcast(name, passphrase, msg, cdc) if err != nil { return err } diff --git a/x/auth/rest/query.go b/x/auth/rest/query.go index dbb53834d8..5629f15476 100644 --- a/x/auth/rest/query.go +++ b/x/auth/rest/query.go @@ -25,8 +25,6 @@ func QueryAccountRequestHandler(storeName string, cdc *wire.Codec, parser sdk.Pa vars := mux.Vars(r) addr := vars["address"] - fmt.Println("ADDR", addr) - bz, err := hex.DecodeString(addr) if err != nil { w.WriteHeader(http.StatusBadRequest) diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 5b692acec5..8e6b10dc78 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -7,6 +7,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -58,14 +59,22 @@ func (c Commander) sendTxCmd(cmd *cobra.Command, args []string) error { } to := sdk.Address(bz) - // build send msg - msg, err := buildMsg(from) + // get account name + name := viper.GetString(client.FlagName) + + // get password + buf := client.BufferStdin() + prompt := fmt.Sprintf("Password to sign with '%s':", name) + passphrase, err := client.GetPassword(prompt, buf) if err != nil { return err } + // build message + msg := BuildMsg(from, to, coins) + // build and sign the transaction, then broadcast to Tendermint - res, err := builder.SignBuildBroadcast(msg, c.cdc) + res, err := builder.SignBuildBroadcast(name, passphrase, msg, c.Cdc) if err != nil { return err } diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 7ce99dadd8..8fd743f515 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -3,11 +3,13 @@ package rest import ( "encoding/hex" "encoding/json" + "io/ioutil" "net/http" "github.com/gorilla/mux" "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" @@ -17,9 +19,11 @@ import ( type SendBody struct { // fees is not used currently // Fees sdk.Coin `json="fees"` - Amount sdk.Coins `json="amount"` - LocalAccountName string `json="account"` - Password string `json="password"` + Amount sdk.Coins `json:"amount"` + LocalAccountName string `json:"name"` + Password string `json:"password"` + ChainID string `json:"chain_id"` + Sequence string `json:"sequence"` } func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { @@ -30,8 +34,13 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request address := vars["address"] var m SendBody - decoder := json.NewDecoder(r.Body) - err := decoder.Decode(&m) + body, err := ioutil.ReadAll(r.Body) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + err = json.Unmarshal(body, &m) if err != nil { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(err.Error())) @@ -60,16 +69,22 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request } to := sdk.Address(bz) - // build - msg := commands.BuildMsg(info.Address(), to, m.Amount) + // build message + msg := commands.BuildMsg(info.PubKey.Address(), to, m.Amount) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } + signMsg := sdk.StdSignMsg{ + ChainID: m.ChainID, + Sequences: []int64{m.Sequence}, + Msg: msg, + } + // sign - txBytes, err := c.SignMessage(msg, kb, m.LocalAccountName, m.Password) + txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, signMsg, c.Cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) From cbbb3be0d4d6a57f6ae001fcebe70e461a2fdd79 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Wed, 14 Mar 2018 16:18:47 +0100 Subject: [PATCH 54/64] fixed tests + removed indexed txs --- client/lcd/lcd_test.go | 72 ++++++++++++++++++++++++------------------ client/tx/root.go | 2 +- tests/tests.go | 24 +++++++++++++- 3 files changed, 66 insertions(+), 32 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index e4be056e25..15b651e8f6 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -14,6 +14,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -198,33 +199,36 @@ func TestCoinSend(t *testing.T) { require.Equal(t, http.StatusNoContent, res.StatusCode, body) // create TX - addr, receiveAddr := doSend(t, port, seed) + addr, receiveAddr, resultTx := doSend(t, port, seed) + + time.Sleep(time.Second * 2) // T + + // check if tx was commited + assert.Equal(t, 0, resultTx.CheckTx.Code) + assert.Equal(t, 0, resultTx.DeliverTx.Code) // query sender res, body = request(t, port, "GET", "/accounts/"+addr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, `{ - "coins": [ - { - "denom": "mycoin", - "amount": 9007199254740991 - } - ] - }`, body) + var m auth.BaseAccount + err := json.Unmarshal([]byte(body), &m) + require.Nil(t, err) + coins := m.Coins + mycoins := coins[0] + assert.Equal(t, "mycoin", mycoins.Denom) + assert.Equal(t, int64(9007199254740991), mycoins.Amount) // query receiver res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, `{ - "coins": [ - { - "denom": "mycoin", - "amount": 1 - } - ] - }`, body) + err = json.Unmarshal([]byte(body), &m) + require.Nil(t, err) + coins = m.Coins + mycoins = coins[0] + assert.Equal(t, "mycoin", mycoins.Denom) + assert.Equal(t, int64(1), mycoins.Amount) } func TestTxs(t *testing.T) { @@ -242,21 +246,25 @@ func TestTxs(t *testing.T) { assert.Equal(t, "[]", body) // create TX - addr, receiveAddr := doSend(t, port, seed) + _, _, resultTx := doSend(t, port, seed) - // query sender - res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", addr), nil) + time.Sleep(time.Second * 2) // TO + + // check if tx is findable + res, body = request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.NotEqual(t, "[]", body) + // // query sender + // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", addr), nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) - // query receiver - res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.receiver='%s'", receiveAddr), nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) + // assert.NotEqual(t, "[]", body) - assert.NotEqual(t, "[]", body) + // // query receiver + // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.receiver='%s'", receiveAddr), nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) - // get TX by hash + // assert.NotEqual(t, "[]", body) } //__________________________________________________________ @@ -268,11 +276,12 @@ func junkInit(t *testing.T) (kill func(), port string, seed string) { require.Nil(t, err) seed = tests.TestInitBasecoin(t, dir) - cmdStart := tests.StartNodeServerForTest(t, dir) + cmdNode := tests.StartNodeServerForTest(t, dir) cmdLCD, port := tests.StartLCDServerForTest(t, dir) + kill = func() { cmdLCD.Process.Kill() - cmdStart.Process.Kill() + cmdNode.Process.Kill() os.Remove(dir) } return kill, port, seed @@ -294,7 +303,7 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res return res, string(output) } -func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr string) { +func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { // create account from seed who has keys var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) res, body := request(t, port, "POST", "/keys", jsonStr) @@ -313,5 +322,8 @@ func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr strin res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) - return sendAddr, receiveAddr + err = json.Unmarshal([]byte(body), &resultTx) + require.Nil(t, err) + + return sendAddr, receiveAddr, resultTx } diff --git a/client/tx/root.go b/client/tx/root.go index 477259e97b..f7d2cf945e 100644 --- a/client/tx/root.go +++ b/client/tx/root.go @@ -22,7 +22,7 @@ func AddCommands(cmd *cobra.Command, cdc *wire.Codec) { } func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { - r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET") + // r.HandleFunc("/txs", SearchTxRequestHandler(cdc)).Methods("GET") r.HandleFunc("/txs/{hash}", QueryTxRequestHandler(cdc)).Methods("GET") // r.HandleFunc("/txs/sign", SignTxRequstHandler).Methods("POST") // r.HandleFunc("/txs/broadcast", BroadcastTxRequestHandler).Methods("POST") diff --git a/tests/tests.go b/tests/tests.go index cd4aa0bf7e..b5eb638f77 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/exec" + "path" "path/filepath" "strings" //"strings" @@ -83,7 +84,28 @@ func TestInitBasecoin(t *testing.T, home string) string { } } - return string(theOutput[seedLine]) + seed := string(theOutput[seedLine]) + + // enable indexing + err = appendToFile(path.Join(home, "config", "config.toml"), "\n\n[tx_indexing]\nindex_all_tags true\n") + require.Nil(t, err) + + return seed +} + +func appendToFile(path string, text string) error { + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0600) + if err != nil { + return err + } + + defer f.Close() + + if _, err = f.WriteString(text); err != nil { + return err + } + + return nil } func makeKeys() error { From 947262f64956a4802ba5b33897d07d1b4f2c5d03 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Wed, 14 Mar 2018 16:47:31 +0100 Subject: [PATCH 55/64] checking for expected sycning response + setting uint32 --- client/lcd/lcd_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 15b651e8f6..47a4be2e3b 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -121,7 +121,9 @@ func TestNodeStatus(t *testing.T) { res, body = request(t, port, "GET", "/syncing", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "true", body) + // we expect that there is no other node running so the syncing state is "false" + // we c + assert.Equal(t, "false", body) } func TestBlock(t *testing.T) { @@ -204,8 +206,8 @@ func TestCoinSend(t *testing.T) { time.Sleep(time.Second * 2) // T // check if tx was commited - assert.Equal(t, 0, resultTx.CheckTx.Code) - assert.Equal(t, 0, resultTx.DeliverTx.Code) + assert.Equal(t, uint32(0), resultTx.CheckTx.Code) + assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender res, body = request(t, port, "GET", "/accounts/"+addr, nil) From fa78893f409686eb68f65d67f2837a2a3cb06b16 Mon Sep 17 00:00:00 2001 From: Matt Bell Date: Wed, 14 Mar 2018 18:28:00 +0100 Subject: [PATCH 56/64] Fixed LCD tests --- client/lcd/lcd_test.go | 14 ++++++++------ tests/tests.go | 11 ++++++++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 47a4be2e3b..2becaeba30 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -237,15 +237,17 @@ func TestTxs(t *testing.T) { kill, port, seed := junkInit(t) defer kill() + // TODO: re-enable once we can get txs by tag + // query wrong - res, body := request(t, port, "GET", "/txs", nil) - require.Equal(t, http.StatusBadRequest, res.StatusCode, body) + // res, body := request(t, port, "GET", "/txs", nil) + // require.Equal(t, http.StatusBadRequest, res.StatusCode, body) // query empty - res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", "8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6"), nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) + // res, body = request(t, port, "GET", fmt.Sprintf("/txs?tag=coin.sender='%s'", "8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6"), nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "[]", body) + // assert.Equal(t, "[]", body) // create TX _, _, resultTx := doSend(t, port, seed) @@ -253,7 +255,7 @@ func TestTxs(t *testing.T) { time.Sleep(time.Second * 2) // TO // check if tx is findable - res, body = request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) + res, body := request(t, port, "GET", fmt.Sprintf("/txs/%s", resultTx.Hash), nil) require.Equal(t, http.StatusOK, res.StatusCode, body) // // query sender diff --git a/tests/tests.go b/tests/tests.go index b5eb638f77..190e152d08 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -62,7 +62,7 @@ func TestInitBasecoin(t *testing.T, home string) string { buf := new(bytes.Buffer) initBasecoind.Stdout = buf - if err := initBasecoind.Start(); err != nil { + if err = initBasecoind.Start(); err != nil { t.Error(err) } @@ -70,7 +70,7 @@ func TestInitBasecoin(t *testing.T, home string) string { require.Nil(t, err) cmdWriter.Close() - if err := initBasecoind.Wait(); err != nil { + if err = initBasecoind.Wait(); err != nil { t.Error(err) } @@ -87,7 +87,7 @@ func TestInitBasecoin(t *testing.T, home string) string { seed := string(theOutput[seedLine]) // enable indexing - err = appendToFile(path.Join(home, "config", "config.toml"), "\n\n[tx_indexing]\nindex_all_tags true\n") + err = appendToFile(path.Join(home, "config", "config.toml"), "\n\n[tx_indexing]\nindex_all_tags = true\n") require.Nil(t, err) return seed @@ -226,6 +226,11 @@ func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { cmd := exec.Command(cmdName, cmdArgs...) err := cmd.Start() require.Nil(t, err) + + // FIXME: if there is a nondeterministic node start failure, + // we should probably make this read the logs to wait for RPC + time.Sleep(time.Second) + return cmd } From 5ea06639f96781e386c0f71acd4ee786177aef64 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Thu, 15 Mar 2018 11:07:40 +0100 Subject: [PATCH 57/64] fix tests and refactored --- client/lcd/lcd_test.go | 16 ++++++++-------- client/tx/broadcast.go | 4 ++-- x/bank/commands/sendtx.go | 2 +- x/bank/rest/root.go | 1 + x/bank/rest/sendtx.go | 10 +++++----- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 2becaeba30..25be57db30 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -24,7 +24,7 @@ import ( ) func TestKeys(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, _ := setupEnvironment(t) defer kill() // empty keys @@ -90,7 +90,7 @@ func TestKeys(t *testing.T) { } func TestVersion(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, _ := setupEnvironment(t) defer kill() // node info @@ -104,7 +104,7 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, _ := setupEnvironment(t) defer kill() // node info @@ -127,7 +127,7 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, _ := setupEnvironment(t) defer kill() time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks @@ -159,7 +159,7 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - kill, port, _ := junkInit(t) + kill, port, _ := setupEnvironment(t) defer kill() time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks @@ -191,7 +191,7 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - kill, port, seed := junkInit(t) + kill, port, seed := setupEnvironment(t) defer kill() time.Sleep(time.Second * 2) // TO @@ -234,7 +234,7 @@ func TestCoinSend(t *testing.T) { } func TestTxs(t *testing.T) { - kill, port, seed := junkInit(t) + kill, port, seed := setupEnvironment(t) defer kill() // TODO: re-enable once we can get txs by tag @@ -275,7 +275,7 @@ func TestTxs(t *testing.T) { // helpers // TODO/XXX: We should be spawning what we need in process, not shelling out -func junkInit(t *testing.T) (kill func(), port string, seed string) { +func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { dir, err := ioutil.TempDir("", "tmp-basecoin-") require.Nil(t, err) diff --git a/client/tx/broadcast.go b/client/tx/broadcast.go index 477571ea50..b9367645fe 100644 --- a/client/tx/broadcast.go +++ b/client/tx/broadcast.go @@ -4,7 +4,7 @@ import ( "encoding/json" "net/http" - "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/builder" ) type BroadcastTxBody struct { @@ -22,7 +22,7 @@ func BroadcastTxRequestHandler(w http.ResponseWriter, r *http.Request) { return } - res, err := client.BroadcastTx([]byte(m.TxBytes)) + res, err := builder.BroadcastTx([]byte(m.TxBytes)) if err != nil { w.WriteHeader(500) w.Write([]byte(err.Error())) diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 8e6b10dc78..43d5b26195 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -100,7 +100,7 @@ func (c Commander) SignMessage(msg sdk.Msg, kb cryptokeys.Keybase, accountName s sigs := []sdk.StdSignature{{ PubKey: pubkey, Signature: sig, - Sequence: viper.GetInt64(flagSequence), + Sequence: viper.GetInt64(client.FlagName), }} // marshal bytes diff --git a/x/bank/rest/root.go b/x/bank/rest/root.go index 67c05ab112..77bb991cb0 100644 --- a/x/bank/rest/root.go +++ b/x/bank/rest/root.go @@ -5,6 +5,7 @@ import ( "github.com/gorilla/mux" ) +// RegisterRoutes - Central function to define routes that get registered by the main application func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc)).Methods("POST") } diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 8fd743f515..70443ab802 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -8,7 +8,6 @@ import ( "github.com/gorilla/mux" - "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" @@ -16,16 +15,17 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank/commands" ) -type SendBody struct { +type sendBody struct { // fees is not used currently // Fees sdk.Coin `json="fees"` Amount sdk.Coins `json:"amount"` LocalAccountName string `json:"name"` Password string `json:"password"` ChainID string `json:"chain_id"` - Sequence string `json:"sequence"` + Sequence int64 `json:"sequence"` } +// SendRequestHandler - http request handler to send coins to a address func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { c := commands.Commander{cdc} return func(w http.ResponseWriter, r *http.Request) { @@ -33,7 +33,7 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request vars := mux.Vars(r) address := vars["address"] - var m SendBody + var m sendBody body, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusBadRequest) @@ -92,7 +92,7 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request } // send - res, err := client.BroadcastTx(txBytes) + res, err := builder.BroadcastTx(txBytes) if err != nil { w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) From 7246a80e3851d705388f6d51b366b9dd6eac2bcf Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Thu, 15 Mar 2018 12:01:16 +0100 Subject: [PATCH 58/64] updated swagger --- docs/sdk/lcd-rest-api.yaml | 483 +++++++++++++++++++------------------ 1 file changed, 250 insertions(+), 233 deletions(-) diff --git a/docs/sdk/lcd-rest-api.yaml b/docs/sdk/lcd-rest-api.yaml index b4ab401e53..eb9ec714bb 100644 --- a/docs/sdk/lcd-rest-api.yaml +++ b/docs/sdk/lcd-rest-api.yaml @@ -46,6 +46,14 @@ paths: other: description: more information on versions type: array + /syncing: + get: + summary: Syncing state of node + description: Get if the node is currently syning with other nodes + responses: + 200: + description: "true" or "false" + /keys: get: summary: List of accounts stored locally @@ -117,9 +125,12 @@ paths: schema: type: object required: - - password + - new_password + - old_password properties: - password: + new_password: + type: string + old_password: type: string responses: 200: @@ -147,35 +158,35 @@ paths: 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/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: @@ -214,12 +225,18 @@ paths: schema: type: object properties: - fees: - $ref: "#/components/schemas/Coins" + name: + type: string + password: + type: string amount: type: array items: $ref: "#/components/schemas/Coins" + chain_id: + type: string + squence: + type: number responses: 202: description: Tx was send and will probably be added to the next block @@ -300,72 +317,72 @@ paths: $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: + # 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 @@ -385,140 +402,140 @@ paths: $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 + # /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: From 07a1f4dc159785a4e3751685793b1edaae7fd603 Mon Sep 17 00:00:00 2001 From: Fabian Weber Date: Thu, 15 Mar 2018 13:41:21 +0100 Subject: [PATCH 59/64] increase timeout for server and add output to console --- tests/tests.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/tests.go b/tests/tests.go index 190e152d08..844b58c2ee 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -227,9 +227,12 @@ func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { err := cmd.Start() require.Nil(t, err) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + // FIXME: if there is a nondeterministic node start failure, // we should probably make this read the logs to wait for RPC - time.Sleep(time.Second) + time.Sleep(time.Second * 2) return cmd } From 683663f680e7957c52535248fd2bac964c05aa8c Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 17 Mar 2018 23:09:04 +0100 Subject: [PATCH 60/64] fixes post rebase --- client/lcd/lcd_test.go | 21 ++++++++++++++++----- server/test_helpers.go | 1 + tests/tests.go | 9 +++++---- types/tx_msg.go | 13 ++++++++++++- x/auth/ante.go | 1 + x/bank/commands/sendtx.go | 5 ++++- x/bank/rest/sendtx.go | 10 ++-------- 7 files changed, 41 insertions(+), 19 deletions(-) diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 25be57db30..9d27248d70 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -7,20 +7,23 @@ import ( "io/ioutil" "net/http" "os" + "path/filepath" "regexp" "testing" "time" - "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/cosmos-sdk/x/auth" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" cryptoKeys "github.com/tendermint/go-crypto/keys" "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmtypes "github.com/tendermint/tendermint/types" + + "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + "github.com/cosmos/cosmos-sdk/tests" + "github.com/cosmos/cosmos-sdk/x/auth" ) func TestKeys(t *testing.T) { @@ -280,12 +283,20 @@ func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { require.Nil(t, err) seed = tests.TestInitBasecoin(t, dir) + // get chain ID + bz, err := ioutil.ReadFile(filepath.Join(dir, "config", "genesis.json")) + require.Nil(t, err) + var gen tmtypes.GenesisDoc + err = json.Unmarshal(bz, &gen) + require.Nil(t, err) cmdNode := tests.StartNodeServerForTest(t, dir) - cmdLCD, port := tests.StartLCDServerForTest(t, dir) + cmdLCD, port := tests.StartLCDServerForTest(t, dir, gen.ChainID) kill = func() { cmdLCD.Process.Kill() + cmdLCD.Process.Wait() cmdNode.Process.Kill() + cmdNode.Process.Wait() os.Remove(dir) } return kill, port, seed diff --git a/server/test_helpers.go b/server/test_helpers.go index e490da3773..103af7c331 100644 --- a/server/test_helpers.go +++ b/server/test_helpers.go @@ -53,6 +53,7 @@ func StartServer(t *testing.T) chan error { viper.Set(flagWithTendermint, true) startCmd := StartCmd(mock.NewApp, log.NewNopLogger()) startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address + startCmd.Flags().Set("rpc.laddr", FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second return RunOrTimeout(startCmd, timeout, t) diff --git a/tests/tests.go b/tests/tests.go index 844b58c2ee..845ac69259 100644 --- a/tests/tests.go +++ b/tests/tests.go @@ -224,11 +224,10 @@ func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { cmdName := whereIsBasecoind() cmdArgs := []string{"start", "--home", home} cmd := exec.Command(cmdName, cmdArgs...) - err := cmd.Start() - require.Nil(t, err) - cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr + err := cmd.Start() + require.Nil(t, err) // FIXME: if there is a nondeterministic node start failure, // we should probably make this read the logs to wait for RPC @@ -238,7 +237,7 @@ func StartNodeServerForTest(t *testing.T, home string) *exec.Cmd { } // expects TestInitBaseCoin to have been run -func StartLCDServerForTest(t *testing.T, home string) (cmd *exec.Cmd, port string) { +func StartLCDServerForTest(t *testing.T, home, chainID string) (cmd *exec.Cmd, port string) { cmdName := whereIsBasecli() port = strings.Split(server.FreeTCPAddr(t), ":")[2] cmdArgs := []string{ @@ -247,6 +246,8 @@ func StartLCDServerForTest(t *testing.T, home string) (cmd *exec.Cmd, port strin home, "--bind", fmt.Sprintf("localhost:%s", port), + "--chain-id", + chainID, } cmd = exec.Command(cmdName, cmdArgs...) cmd.Stdout = os.Stdout diff --git a/types/tx_msg.go b/types/tx_msg.go index 21bc330540..e4af1d8040 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -1,6 +1,9 @@ package types -import "encoding/json" +import ( + "encoding/json" + "fmt" +) // Transactions messages must fulfill the Msg type Msg interface { @@ -90,6 +93,13 @@ func NewStdFee(gas int64, amount ...Coin) StdFee { } func (fee StdFee) Bytes() []byte { + // normalize. XXX + // this is a sign of something ugly + // (in the lcd_test, client side its null, + // server side its []) + if len(fee.Amount) == 0 { + fee.Amount = Coins{} + } bz, err := json.Marshal(fee) // TODO if err != nil { panic(err) @@ -115,6 +125,7 @@ type StdSignDoc struct { // StdSignBytes returns the bytes to sign for a transaction. // TODO: change the API to just take a chainID and StdTx ? func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg Msg) []byte { + fmt.Println("FEE BYTES BABY", fee, string(fee.Bytes())) bz, err := json.Marshal(StdSignDoc{ ChainID: chainID, Sequences: sequences, diff --git a/x/auth/ante.go b/x/auth/ante.go index 43b9bb8341..08bb185778 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -125,6 +125,7 @@ func processSig( return nil, sdk.ErrInternal("setting PubKey on signer's account").Result() } } + // Check sig. if !pubKey.VerifyBytes(signBytes, sig.Signature) { return nil, sdk.ErrUnauthorized("signature verification failed").Result() diff --git a/x/bank/commands/sendtx.go b/x/bank/commands/sendtx.go index 43d5b26195..5d1a6e05cf 100644 --- a/x/bank/commands/sendtx.go +++ b/x/bank/commands/sendtx.go @@ -103,8 +103,11 @@ func (c Commander) SignMessage(msg sdk.Msg, kb cryptokeys.Keybase, accountName s Sequence: viper.GetInt64(client.FlagName), }} + // TODO: fees + var fee sdk.StdFee + // marshal bytes - tx := sdk.NewStdTx(msg, sigs) + tx := sdk.NewStdTx(msg, fee, sigs) txBytes, err := c.Cdc.MarshalBinary(tx) if err != nil { diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 70443ab802..407a53420e 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -71,20 +71,14 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request // build message msg := commands.BuildMsg(info.PubKey.Address(), to, m.Amount) - if err != nil { + if err != nil { // XXX rechecking same error ? w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(err.Error())) return } - signMsg := sdk.StdSignMsg{ - ChainID: m.ChainID, - Sequences: []int64{m.Sequence}, - Msg: msg, - } - // sign - txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, signMsg, c.Cdc) + txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte(err.Error())) From 7f3a6e0c042250987adf936abd72a98378510837 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Wed, 14 Mar 2018 20:22:06 +0100 Subject: [PATCH 61/64] Options -> AppState --- client/keys.go | 4 +- examples/basecoin/cmd/basecli/main.go | 3 +- examples/basecoin/cmd/basecoind/main.go | 1 + server/init.go | 79 +++++-------------------- server/start.go | 16 ++--- 5 files changed, 27 insertions(+), 76 deletions(-) diff --git a/client/keys.go b/client/keys.go index 7e150de7a2..47eb0b9c95 100644 --- a/client/keys.go +++ b/client/keys.go @@ -6,8 +6,8 @@ import ( dbm "github.com/tendermint/tmlibs/db" ) -// TODO explain what the keybase is used for -// GetKeyBase initializes a keybase based on the configuration +// GetKeyBase initializes a keybase based on the given db. +// The KeyBase manages all activity requiring access to a key. func GetKeyBase(db dbm.DB) keys.Keybase { keybase := keys.New( db, diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 7f7377b483..5260a90f7f 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -13,13 +13,14 @@ import ( "github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" - coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands" + "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" + coolcmd "github.com/cosmos/cosmos-sdk/examples/basecoin/x/cool/commands" ) // gaiacliCmd is the entry point for this binary diff --git a/examples/basecoin/cmd/basecoind/main.go b/examples/basecoin/cmd/basecoind/main.go index 44ea00fbba..56dfcc02f2 100644 --- a/examples/basecoin/cmd/basecoind/main.go +++ b/examples/basecoin/cmd/basecoind/main.go @@ -59,6 +59,7 @@ func generateApp(rootDir string, logger log.Logger) (abci.Application, error) { } func main() { + // TODO: set logger through CLI logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "main") diff --git a/server/init.go b/server/init.go index f2134abcd0..e506720f8c 100644 --- a/server/init.go +++ b/server/init.go @@ -2,30 +2,24 @@ package server import ( "encoding/json" - "fmt" "io/ioutil" "github.com/spf13/cobra" - "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" - - sdk "github.com/cosmos/cosmos-sdk/types" ) // InitCmd will initialize all files for tendermint, -// along with proper app_options. +// along with proper app_state. // The application can pass in a function to generate -// proper options. And may want to use GenerateCoinKey +// proper state. And may want to use GenerateCoinKey // to create default account(s). -func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command { +func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command { cmd := initCmd{ gen: gen, logger: logger, @@ -37,39 +31,14 @@ func InitCmd(gen GenOptions, logger log.Logger) *cobra.Command { } } -// GenOptions can parse command-line and flag to -// generate default app_options for the genesis file. +// GenAppState can parse command-line and flag to +// generate default app_state 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() (sdk.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 GenAppState func(args []string) (json.RawMessage, error) type initCmd struct { - gen GenOptions - logger log.Logger + genAppState GenAppState + logger log.Logger } func (c initCmd) run(cmd *cobra.Command, args []string) error { @@ -85,19 +54,19 @@ func (c initCmd) run(cmd *cobra.Command, args []string) error { } // no app_options, leave like tendermint - if c.gen == nil { + if c.genAppState == nil { return nil } - // Now, we want to add the custom app_options - options, err := c.gen(args) + // Now, we want to add the custom app_state + appState, err := c.genAppState(args) if err != nil { return err } // And add them to the genesis file genFile := config.GenesisFile() - return addGenesisOptions(genFile, options) + return addGenesisState(genFile, appState) } // This was copied from tendermint/cmd/tendermint/commands/init.go @@ -141,7 +110,7 @@ func (c initCmd) initTendermintFiles(config *cfg.Config) error { // so we can add one line. type GenesisDoc map[string]json.RawMessage -func addGenesisOptions(filename string, options json.RawMessage) error { +func addGenesisState(filename string, appState json.RawMessage) error { bz, err := ioutil.ReadFile(filename) if err != nil { return err @@ -153,7 +122,7 @@ func addGenesisOptions(filename string, options json.RawMessage) error { return err } - doc["app_state"] = options + doc["app_state"] = appState out, err := json.MarshalIndent(doc, "", " ") if err != nil { return err @@ -161,23 +130,3 @@ func addGenesisOptions(filename string, options json.RawMessage) error { 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) -} diff --git a/server/start.go b/server/start.go index 1424c81532..530a3a3e4b 100644 --- a/server/start.go +++ b/server/start.go @@ -23,14 +23,14 @@ const ( // appGenerator lets us lazily initialize app, using home dir // and other flags (?) to start -type appGenerator func(string, log.Logger) (abci.Application, error) +type appCreator 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 { +func StartCmd(app appCreator, logger log.Logger) *cobra.Command { start := startCmd{ - app: app, - logger: logger, + appCreator: appCreator, + logger: logger, } cmd := &cobra.Command{ Use: "start", @@ -48,8 +48,8 @@ func StartCmd(app appGenerator, logger log.Logger) *cobra.Command { } type startCmd struct { - app appGenerator - logger log.Logger + appCreator appCreator + logger log.Logger } func (s startCmd) run(cmd *cobra.Command, args []string) error { @@ -65,7 +65,7 @@ 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) + app, err := s.appCreator(home, s.logger) if err != nil { return err } @@ -92,7 +92,7 @@ func (s startCmd) startInProcess() error { } home := cfg.RootDir - app, err := s.app(home, s.logger) + app, err := s.appCreator(home, s.logger) if err != nil { return err } From d807d32f8a980a96062441e34b3119d9426e3d01 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 15 Mar 2018 03:16:54 +0100 Subject: [PATCH 62/64] client/lcd: wip use in-proc tm and lcd for tests --- client/lcd/lcd_test.go | 65 ++++++++++++++++++++++----- client/lcd/main_test.go | 24 ++++++++++ client/lcd/root.go | 32 ++++++++----- examples/basecoin/cmd/basecli/main.go | 4 ++ x/bank/rest/root.go | 9 ++-- x/bank/rest/sendtx.go | 10 +---- 6 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 client/lcd/main_test.go diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 9d27248d70..55ddcfe09c 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "net/http" "os" "path/filepath" @@ -12,18 +13,18 @@ import ( "testing" "time" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - cryptoKeys "github.com/tendermint/go-crypto/keys" - "github.com/tendermint/tendermint/p2p" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - tmtypes "github.com/tendermint/tendermint/types" - - "github.com/cosmos/cosmos-sdk/client" + "github.com/chain/core/config" + client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/wire" + auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + p2p "github.com/tendermint/go-p2p" + "github.com/tendermint/mintdb/types" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tmlibs/log" ) func TestKeys(t *testing.T) { @@ -299,9 +300,53 @@ func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { cmdNode.Process.Wait() os.Remove(dir) } +} + +// strt TM and the LCD in process, listening on their respective sockets +func startTMAndLCD(t *testing.T) (kill func(), port string, seed string) { + + // make the keybase and its key ... + + startTM(cfg, genDoc, app) + startLCD(cdc, listenAddr, logger) + + kill = func() { + // TODO: cleanup + // TODO: it would be great if TM could run without + // persiting anything in the first place + } return kill, port, seed } +// Create & start in-process tendermint node with memdb +// and in-process abci application. +// TODO: need to clean up the WAL dir or enable it to be not persistent +func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) (*Node, error) { + genDocProvider := func() (*types.GenesisDoc, error) { return genDoc, nil } + dbProvider := func() (*dbm.DB, error) { return dbm.NewMemDB(), nil } + n, err := node.NewNode(cfg, + privVal, + proxy.NewLocalClientCreator(app), + genDocProvider, + dbProvider, + logger.With("module", "node")) + if err != nil { + return nil, err + } + + err = n.Start() + if err != nil { + return nil, err + } + return n, err +} + +// start the LCD. note this blocks! +func startLCD(cdc *wire.Codec, listenAddr string, logger log.Logger) (net.Listener, error) { + handler := createHandler(cdc) + return StartHTTPServer(listenAddr, handler, logger) +} + func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { var res *http.Response var err error diff --git a/client/lcd/main_test.go b/client/lcd/main_test.go new file mode 100644 index 0000000000..4043098217 --- /dev/null +++ b/client/lcd/main_test.go @@ -0,0 +1,24 @@ +package client_test + +import ( + "os" + "testing" + + nm "github.com/tendermint/tendermint/node" +) + +var node *nm.Node + +// See https://golang.org/pkg/testing/#hdr-Main +// for more details +func TestMain(m *testing.M) { + // start a basecoind node and LCD server in the background to test against + + // run all the tests against a single server instance + code := m.Run() + + // tear down + + // + os.Exit(code) +} diff --git a/client/lcd/root.go b/client/lcd/root.go index 8476d16d5e..b0ffa2f6da 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -2,10 +2,12 @@ package lcd import ( "net/http" + "os" "github.com/gorilla/mux" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tendermint/tmlibs/log" client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" @@ -18,8 +20,8 @@ import ( ) const ( - flagBind = "bind" - flagCORS = "cors" + flagListenAddr = "laddr" + flagCORS = "cors" ) // ServeCommand will generate a long-running rest server @@ -29,25 +31,35 @@ func ServeCommand(cdc *wire.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "rest-server", Short: "Start LCD (light-client daemon), a local REST server", - RunE: startRESTServer(cdc), + RunE: startRESTServerFn(cdc), } - // TODO: handle unix sockets also? - cmd.Flags().StringP(flagBind, "b", "localhost:1317", "Interface and port that server binds to") + cmd.Flags().StringP(flagListenAddr, "a", "tcp://localhost:1317", "Address for server to listen on") 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 } -func startRESTServer(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { +func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { - bind := viper.GetString(flagBind) - r := initRouter(cdc) - return http.ListenAndServe(bind, r) + listenAddr := viper.GetString(flagListenAddr) + handler := createHandler(cdc) + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). + With("module", "rest-server") + listener, err := StartHTTPServer(listenAddr, handler, logger) + if err != nil { + return err + } + + // Wait forever and cleanup + cmn.TrapSignal(func() { + err := listener.Close() + logger.Error("Error closing listener", "err", err) + }) } } -func initRouter(cdc *wire.Codec) http.Handler { +func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") diff --git a/examples/basecoin/cmd/basecli/main.go b/examples/basecoin/cmd/basecli/main.go index 5260a90f7f..5f7b337900 100644 --- a/examples/basecoin/cmd/basecli/main.go +++ b/examples/basecoin/cmd/basecli/main.go @@ -42,6 +42,10 @@ func main() { // get the codec cdc := app.MakeCodec() + // TODO: setup keybase, viper object, etc. to be passed into + // the below functions and eliminate global vars, like we do + // with the cdc + // add standard rpc, and tx commands rpc.AddCommands(basecliCmd) basecliCmd.AddCommand(client.LineBreak) diff --git a/x/bank/rest/root.go b/x/bank/rest/root.go index 77bb991cb0..4534482a93 100644 --- a/x/bank/rest/root.go +++ b/x/bank/rest/root.go @@ -1,11 +1,14 @@ package rest import ( - "github.com/cosmos/cosmos-sdk/wire" "github.com/gorilla/mux" + + keys "github.com/tendermint/go-crypto/keys" + + "github.com/cosmos/cosmos-sdk/wire" ) // RegisterRoutes - Central function to define routes that get registered by the main application -func RegisterRoutes(r *mux.Router, cdc *wire.Codec) { - r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc)).Methods("POST") +func RegisterRoutes(r *mux.Router, cdc *wire.Codec, kb keys.Keybase) { + r.HandleFunc("/accounts/{address}/send", SendRequestHandler(cdc, kb)).Methods("POST") } diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 407a53420e..4b1c8f12a0 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -7,6 +7,7 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/tendermint/go-crypto/keys" "github.com/cosmos/cosmos-sdk/client/builder" "github.com/cosmos/cosmos-sdk/client/keys" @@ -26,7 +27,7 @@ type sendBody struct { } // SendRequestHandler - http request handler to send coins to a address -func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request) { +func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWriter, *http.Request) { c := commands.Commander{cdc} return func(w http.ResponseWriter, r *http.Request) { // collect data @@ -47,13 +48,6 @@ func SendRequestHandler(cdc *wire.Codec) func(http.ResponseWriter, *http.Request return } - kb, err := keys.GetKeyBase() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte(err.Error())) - return - } - info, err := kb.Get(m.LocalAccountName) if err != nil { w.WriteHeader(http.StatusUnauthorized) From 64852138b6aedc4bd3c8dabc4b9ce3385734700f Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 18 Mar 2018 01:42:18 +0100 Subject: [PATCH 63/64] fix the tests. includes some hacks --- client/builder/builder.go | 1 + client/lcd/helpers.go | 69 +++++++++++ client/lcd/lcd_test.go | 252 +++++++++++++++++++++++--------------- client/lcd/main_test.go | 18 ++- client/lcd/root.go | 15 ++- server/init.go | 4 +- server/key.go | 34 +++++ server/start.go | 2 +- server/start_test.go | 6 +- types/tx_msg.go | 2 - x/auth/ante.go | 7 ++ x/bank/rest/sendtx.go | 5 +- 12 files changed, 301 insertions(+), 114 deletions(-) create mode 100644 client/lcd/helpers.go create mode 100644 server/key.go diff --git a/client/builder/builder.go b/client/builder/builder.go index 69c95fa3b6..2fb1c824e8 100644 --- a/client/builder/builder.go +++ b/client/builder/builder.go @@ -106,6 +106,7 @@ func SignAndBuild(name, passphrase string, msg sdk.Msg, cdc *wire.Codec) ([]byte // sign and build bz := signMsg.Bytes() + sig, pubkey, err := keybase.Sign(name, passphrase, bz) if err != nil { return nil, err diff --git a/client/lcd/helpers.go b/client/lcd/helpers.go new file mode 100644 index 0000000000..71278fca33 --- /dev/null +++ b/client/lcd/helpers.go @@ -0,0 +1,69 @@ +package lcd + +// NOTE: COPIED VERBATIM FROM tendermint/tendermint/rpc/test/helpers.go + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + cmn "github.com/tendermint/tmlibs/common" + + cfg "github.com/tendermint/tendermint/config" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + rpcclient "github.com/tendermint/tendermint/rpc/lib/client" +) + +var globalConfig *cfg.Config + +func waitForRPC() { + laddr := GetConfig().RPC.ListenAddress + fmt.Println("LADDR", laddr) + client := rpcclient.NewJSONRPCClient(laddr) + result := new(ctypes.ResultStatus) + for { + _, err := client.Call("status", map[string]interface{}{}, result) + if err == nil { + return + } + } +} + +// f**ing long, but unique for each test +func makePathname() string { + // get path + p, err := os.Getwd() + if err != nil { + panic(err) + } + // fmt.Println(p) + sep := string(filepath.Separator) + return strings.Replace(p, sep, "_", -1) +} + +func randPort() int { + return int(cmn.RandUint16()/2 + 10000) +} + +func makeAddrs() (string, string, string) { + start := randPort() + return fmt.Sprintf("tcp://0.0.0.0:%d", start), + fmt.Sprintf("tcp://0.0.0.0:%d", start+1), + fmt.Sprintf("tcp://0.0.0.0:%d", start+2) +} + +// GetConfig returns a config for the test cases as a singleton +func GetConfig() *cfg.Config { + if globalConfig == nil { + pathname := makePathname() + globalConfig = cfg.ResetTestRoot(pathname) + + // and we use random ports to run in parallel + tm, rpc, _ := makeAddrs() + globalConfig.P2P.ListenAddress = tm + globalConfig.RPC.ListenAddress = rpc + globalConfig.TxIndex.IndexTags = "app.creator" // see kvstore application + } + return globalConfig +} diff --git a/client/lcd/lcd_test.go b/client/lcd/lcd_test.go index 55ddcfe09c..99c040f1a3 100644 --- a/client/lcd/lcd_test.go +++ b/client/lcd/lcd_test.go @@ -8,50 +8,76 @@ import ( "net" "net/http" "os" - "path/filepath" "regexp" "testing" "time" - "github.com/chain/core/config" - client "github.com/cosmos/cosmos-sdk/client" - keys "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/tests" - "github.com/cosmos/cosmos-sdk/wire" - auth "github.com/cosmos/cosmos-sdk/x/auth/rest" + "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - p2p "github.com/tendermint/go-p2p" - "github.com/tendermint/mintdb/types" + + abci "github.com/tendermint/abci/types" + cryptoKeys "github.com/tendermint/go-crypto/keys" + tmcfg "github.com/tendermint/tendermint/config" + nm "github.com/tendermint/tendermint/node" + p2p "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/proxy" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + tmrpc "github.com/tendermint/tendermint/rpc/lib/server" + tmtypes "github.com/tendermint/tendermint/types" + dbm "github.com/tendermint/tmlibs/db" "github.com/tendermint/tmlibs/log" + + client "github.com/cosmos/cosmos-sdk/client" + keys "github.com/cosmos/cosmos-sdk/client/keys" + bapp "github.com/cosmos/cosmos-sdk/examples/basecoin/app" + btypes "github.com/cosmos/cosmos-sdk/examples/basecoin/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/wire" + "github.com/cosmos/cosmos-sdk/x/auth" +) + +var ( + coinDenom = "mycoin" + coinAmount = int64(10000000) + + // XXX bad globals + port string // XXX: but it's the int ... + name string = "test" + password string = "0123456789" + seed string + sendAddr string ) func TestKeys(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // empty keys - res, body := request(t, port, "GET", "/keys", nil) - require.Equal(t, http.StatusOK, res.StatusCode, body) - assert.Equal(t, "[]", body, "Expected an empty array") + // XXX: the test comes with a key setup + /* + res, body := request(t, port, "GET", "/keys", nil) + require.Equal(t, http.StatusOK, res.StatusCode, body) + assert.Equal(t, "[]", body, "Expected an empty array") + */ // get seed - res, body = request(t, port, "GET", "/keys/seed", nil) + res, body := request(t, port, "GET", "/keys/seed", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - seed := body + newSeed := body reg, err := regexp.Compile(`([a-z]+ ){12}`) require.Nil(t, err) match := reg.MatchString(seed) assert.True(t, match, "Returned seed has wrong foramt", seed) + newName := "test_newname" + newPassword := "0987654321" + // add key - var jsonStr = []byte(`{"name":"test_fail", "password":"1234567890"}`) + var jsonStr = []byte(fmt.Sprintf(`{"name":"test_fail", "password":"%s"}`, password)) res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusBadRequest, res.StatusCode, "Account creation should require a seed") - jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) + jsonStr = []byte(fmt.Sprintf(`{"name":"%s", "password":"%s", "seed": "%s"}`, newName, newPassword, newSeed)) res, body = request(t, port, "POST", "/keys", jsonStr) assert.Equal(t, http.StatusOK, res.StatusCode, body) @@ -61,41 +87,42 @@ func TestKeys(t *testing.T) { // existing keys res, body = request(t, port, "GET", "/keys", nil) require.Equal(t, http.StatusOK, res.StatusCode, body) - var m [1]keys.KeyOutput + var m [2]keys.KeyOutput err = json.Unmarshal([]byte(body), &m) require.Nil(t, err) - assert.Equal(t, m[0].Name, "test", "Did not serve keys name correctly") - assert.Equal(t, m[0].Address, addr, "Did not serve keys Address correctly") + assert.Equal(t, m[0].Name, name, "Did not serve keys name correctly") + assert.Equal(t, m[0].Address, sendAddr, "Did not serve keys Address correctly") + assert.Equal(t, m[1].Name, newName, "Did not serve keys name correctly") + assert.Equal(t, m[1].Address, addr, "Did not serve keys Address correctly") // select key - res, body = request(t, port, "GET", "/keys/test", nil) + keyEndpoint := fmt.Sprintf("/keys/%s", newName) + res, body = request(t, port, "GET", keyEndpoint, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m2 keys.KeyOutput err = json.Unmarshal([]byte(body), &m2) require.Nil(t, err) - assert.Equal(t, "test", m2.Name, "Did not serve keys name correctly") + assert.Equal(t, newName, m2.Name, "Did not serve keys name correctly") assert.Equal(t, addr, m2.Address, "Did not serve keys Address correctly") // update key - jsonStr = []byte(`{"old_password":"1234567890", "new_password":"12345678901"}`) - res, body = request(t, port, "PUT", "/keys/test", jsonStr) + jsonStr = []byte(fmt.Sprintf(`{"old_password":"%s", "new_password":"12345678901"}`, newPassword)) + res, body = request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) // here it should say unauthorized as we changed the password before - res, body = request(t, port, "PUT", "/keys/test", jsonStr) + res, body = request(t, port, "PUT", keyEndpoint, jsonStr) require.Equal(t, http.StatusUnauthorized, res.StatusCode, body) // delete key jsonStr = []byte(`{"password":"12345678901"}`) - res, body = request(t, port, "DELETE", "/keys/test", jsonStr) + res, body = request(t, port, "DELETE", keyEndpoint, jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) } func TestVersion(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // node info res, body := request(t, port, "GET", "/version", nil) @@ -108,8 +135,6 @@ func TestVersion(t *testing.T) { } func TestNodeStatus(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() // node info res, body := request(t, port, "GET", "/node_info", nil) @@ -131,8 +156,6 @@ func TestNodeStatus(t *testing.T) { } func TestBlock(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks @@ -163,10 +186,6 @@ func TestBlock(t *testing.T) { } func TestValidators(t *testing.T) { - kill, port, _ := setupEnvironment(t) - defer kill() - - time.Sleep(time.Second * 2) // TODO: LOL -> wait for blocks var resultVals ctypes.ResultValidators @@ -195,17 +214,13 @@ func TestValidators(t *testing.T) { } func TestCoinSend(t *testing.T) { - kill, port, seed := setupEnvironment(t) - defer kill() - - time.Sleep(time.Second * 2) // TO // query empty res, body := request(t, port, "GET", "/accounts/8FA6AB57AD6870F6B5B2E57735F38F2F30E73CB6", nil) require.Equal(t, http.StatusNoContent, res.StatusCode, body) // create TX - addr, receiveAddr, resultTx := doSend(t, port, seed) + receiveAddr, resultTx := doSend(t, port, seed) time.Sleep(time.Second * 2) // T @@ -214,7 +229,7 @@ func TestCoinSend(t *testing.T) { assert.Equal(t, uint32(0), resultTx.DeliverTx.Code) // query sender - res, body = request(t, port, "GET", "/accounts/"+addr, nil) + res, body = request(t, port, "GET", "/accounts/"+sendAddr, nil) require.Equal(t, http.StatusOK, res.StatusCode, body) var m auth.BaseAccount @@ -222,8 +237,8 @@ func TestCoinSend(t *testing.T) { require.Nil(t, err) coins := m.Coins mycoins := coins[0] - assert.Equal(t, "mycoin", mycoins.Denom) - assert.Equal(t, int64(9007199254740991), mycoins.Amount) + assert.Equal(t, coinDenom, mycoins.Denom) + assert.Equal(t, coinAmount-1, mycoins.Amount) // query receiver res, body = request(t, port, "GET", "/accounts/"+receiveAddr, nil) @@ -233,13 +248,11 @@ func TestCoinSend(t *testing.T) { require.Nil(t, err) coins = m.Coins mycoins = coins[0] - assert.Equal(t, "mycoin", mycoins.Denom) + assert.Equal(t, coinDenom, mycoins.Denom) assert.Equal(t, int64(1), mycoins.Amount) } func TestTxs(t *testing.T) { - kill, port, seed := setupEnvironment(t) - defer kill() // TODO: re-enable once we can get txs by tag @@ -254,7 +267,7 @@ func TestTxs(t *testing.T) { // assert.Equal(t, "[]", body) // create TX - _, _, resultTx := doSend(t, port, seed) + _, resultTx := doSend(t, port, seed) time.Sleep(time.Second * 2) // TO @@ -278,53 +291,85 @@ func TestTxs(t *testing.T) { //__________________________________________________________ // helpers -// TODO/XXX: We should be spawning what we need in process, not shelling out -func setupEnvironment(t *testing.T) (kill func(), port string, seed string) { - dir, err := ioutil.TempDir("", "tmp-basecoin-") - require.Nil(t, err) - - seed = tests.TestInitBasecoin(t, dir) - // get chain ID - bz, err := ioutil.ReadFile(filepath.Join(dir, "config", "genesis.json")) - require.Nil(t, err) - var gen tmtypes.GenesisDoc - err = json.Unmarshal(bz, &gen) - require.Nil(t, err) - cmdNode := tests.StartNodeServerForTest(t, dir) - cmdLCD, port := tests.StartLCDServerForTest(t, dir, gen.ChainID) - - kill = func() { - cmdLCD.Process.Kill() - cmdLCD.Process.Wait() - cmdNode.Process.Kill() - cmdNode.Process.Wait() - os.Remove(dir) - } -} - // strt TM and the LCD in process, listening on their respective sockets -func startTMAndLCD(t *testing.T) (kill func(), port string, seed string) { +func startTMAndLCD() (*nm.Node, net.Listener, error) { - // make the keybase and its key ... - - startTM(cfg, genDoc, app) - startLCD(cdc, listenAddr, logger) - - kill = func() { - // TODO: cleanup - // TODO: it would be great if TM could run without - // persiting anything in the first place + kb, err := keys.GetKeyBase() // dbm.NewMemDB()) // :( + if err != nil { + return nil, nil, err } - return kill, port, seed + var info cryptoKeys.Info + info, seed, err = kb.Create(name, password, cryptoKeys.AlgoEd25519) // XXX global seed + if err != nil { + return nil, nil, err + } + + pubKey := info.PubKey + sendAddr = pubKey.Address().String() // XXX global + + config := GetConfig() + config.Consensus.TimeoutCommit = 1000 + config.Consensus.SkipTimeoutCommit = false + + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)) + logger = log.NewFilter(logger, log.AllowError()) + privValidatorFile := config.PrivValidatorFile() + privVal := tmtypes.LoadOrGenPrivValidatorFS(privValidatorFile) + app := bapp.NewBasecoinApp(logger, dbm.NewMemDB()) + + genesisFile := config.GenesisFile() + genDoc, err := tmtypes.GenesisDocFromFile(genesisFile) + if err != nil { + return nil, nil, err + } + + coins := sdk.Coins{{coinDenom, coinAmount}} + appState := btypes.GenesisState{ + Accounts: []*btypes.GenesisAccount{ + { + Name: "tester", + Address: pubKey.Address(), + Coins: coins, + }, + }, + } + stateBytes, err := json.Marshal(appState) + if err != nil { + return nil, nil, err + } + genDoc.AppState = stateBytes + + cdc := wire.NewCodec() + + // LCD listen address + port = fmt.Sprintf("%d", 17377) // XXX + listenAddr := fmt.Sprintf("tcp://localhost:%s", port) // XXX + + // XXX: need to set this so LCD knows the tendermint node address! + viper.Set(client.FlagNode, config.RPC.ListenAddress) + viper.Set(client.FlagChainID, genDoc.ChainID) + + node, err := startTM(config, logger, genDoc, privVal, app) + if err != nil { + return nil, nil, err + } + lcd, err := startLCD(cdc, logger, listenAddr) + if err != nil { + return nil, nil, err + } + + time.Sleep(time.Second * 2) + + return node, lcd, nil } // Create & start in-process tendermint node with memdb // and in-process abci application. // TODO: need to clean up the WAL dir or enable it to be not persistent -func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) (*Node, error) { - genDocProvider := func() (*types.GenesisDoc, error) { return genDoc, nil } - dbProvider := func() (*dbm.DB, error) { return dbm.NewMemDB(), nil } - n, err := node.NewNode(cfg, +func startTM(cfg *tmcfg.Config, logger log.Logger, genDoc *tmtypes.GenesisDoc, privVal tmtypes.PrivValidator, app abci.Application) (*nm.Node, error) { + genDocProvider := func() (*tmtypes.GenesisDoc, error) { return genDoc, nil } + dbProvider := func(*nm.DBContext) (dbm.DB, error) { return dbm.NewMemDB(), nil } + n, err := nm.NewNode(cfg, privVal, proxy.NewLocalClientCreator(app), genDocProvider, @@ -338,13 +383,18 @@ func startTM(cfg *config.Config, genDoc types.GenesisDoc, app abci.Application) if err != nil { return nil, err } + + // wait for rpc + waitForRPC() + + logger.Info("Tendermint running!") return n, err } // start the LCD. note this blocks! -func startLCD(cdc *wire.Codec, listenAddr string, logger log.Logger) (net.Listener, error) { +func startLCD(cdc *wire.Codec, logger log.Logger, listenAddr string) (net.Listener, error) { handler := createHandler(cdc) - return StartHTTPServer(listenAddr, handler, logger) + return tmrpc.StartHTTPServer(listenAddr, handler, logger) } func request(t *testing.T, port, method, path string, payload []byte) (*http.Response, string) { @@ -363,13 +413,7 @@ func request(t *testing.T, port, method, path string, payload []byte) (*http.Res return res, string(output) } -func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { - // create account from seed who has keys - var jsonStr = []byte(fmt.Sprintf(`{"name":"test", "password":"1234567890", "seed": "%s"}`, seed)) - res, body := request(t, port, "POST", "/keys", jsonStr) - - assert.Equal(t, http.StatusOK, res.StatusCode, body) - sendAddr = body +func doSend(t *testing.T, port, seed string) (receiveAddr string, resultTx ctypes.ResultBroadcastTxCommit) { // create receive address kb := client.MockKeyBase() @@ -377,13 +421,23 @@ func doSend(t *testing.T, port, seed string) (sendAddr string, receiveAddr strin require.Nil(t, err) receiveAddr = receiveInfo.PubKey.Address().String() + // get the account to get the sequence + res, body := request(t, port, "GET", "/accounts/"+sendAddr, nil) + // require.Equal(t, http.StatusOK, res.StatusCode, body) + acc := auth.BaseAccount{} + err = json.Unmarshal([]byte(body), &acc) + require.Nil(t, err) + fmt.Println("BODY", body) + fmt.Println("ACC", acc) + sequence := acc.Sequence + // send - jsonStr = []byte(`{ "name":"test", "password":"1234567890", "amount":[{ "denom": "mycoin", "amount": 1 }] }`) + jsonStr := []byte(fmt.Sprintf(`{ "name":"%s", "password":"%s", "sequence":%d, "amount":[{ "denom": "%s", "amount": 1 }] }`, name, password, sequence, coinDenom)) res, body = request(t, port, "POST", "/accounts/"+receiveAddr+"/send", jsonStr) require.Equal(t, http.StatusOK, res.StatusCode, body) err = json.Unmarshal([]byte(body), &resultTx) require.Nil(t, err) - return sendAddr, receiveAddr, resultTx + return receiveAddr, resultTx } diff --git a/client/lcd/main_test.go b/client/lcd/main_test.go index 4043098217..9f0e2bd4f0 100644 --- a/client/lcd/main_test.go +++ b/client/lcd/main_test.go @@ -1,6 +1,7 @@ -package client_test +package lcd import ( + "fmt" "os" "testing" @@ -15,10 +16,23 @@ func TestMain(m *testing.M) { // start a basecoind node and LCD server in the background to test against // run all the tests against a single server instance + node, lcd, err := startTMAndLCD() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + code := m.Run() // tear down + // TODO: cleanup + // TODO: it would be great if TM could run without + // persiting anything in the first place + node.Stop() + node.Wait() + + // just a listener ... + lcd.Close() - // os.Exit(code) } diff --git a/client/lcd/root.go b/client/lcd/root.go index b0ffa2f6da..7f18af59dc 100644 --- a/client/lcd/root.go +++ b/client/lcd/root.go @@ -9,6 +9,9 @@ import ( "github.com/spf13/viper" "github.com/tendermint/tmlibs/log" + tmserver "github.com/tendermint/tendermint/rpc/lib/server" + cmn "github.com/tendermint/tmlibs/common" + client "github.com/cosmos/cosmos-sdk/client" keys "github.com/cosmos/cosmos-sdk/client/keys" rpc "github.com/cosmos/cosmos-sdk/client/rpc" @@ -46,7 +49,7 @@ func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) handler := createHandler(cdc) logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)). With("module", "rest-server") - listener, err := StartHTTPServer(listenAddr, handler, logger) + listener, err := tmserver.StartHTTPServer(listenAddr, handler, logger) if err != nil { return err } @@ -56,6 +59,7 @@ func startRESTServerFn(cdc *wire.Codec) func(cmd *cobra.Command, args []string) err := listener.Close() logger.Error("Error closing listener", "err", err) }) + return nil } } @@ -63,11 +67,16 @@ func createHandler(cdc *wire.Codec) http.Handler { r := mux.NewRouter() r.HandleFunc("/version", version.VersionRequestHandler).Methods("GET") + kb, err := keys.GetKeyBase() //XXX + if err != nil { + panic(err) + } + // TODO make more functional? aka r = keys.RegisterRoutes(r) keys.RegisterRoutes(r) rpc.RegisterRoutes(r) tx.RegisterRoutes(r, cdc) - auth.RegisterRoutes(r, cdc, "main") // TODO should use a variable not just a string - bank.RegisterRoutes(r, cdc) + auth.RegisterRoutes(r, cdc, "main") + bank.RegisterRoutes(r, cdc, kb) return r } diff --git a/server/init.go b/server/init.go index e506720f8c..12e330dbc8 100644 --- a/server/init.go +++ b/server/init.go @@ -21,8 +21,8 @@ import ( // to create default account(s). func InitCmd(gen GenAppState, logger log.Logger) *cobra.Command { cmd := initCmd{ - gen: gen, - logger: logger, + genAppState: gen, + logger: logger, } return &cobra.Command{ Use: "init", diff --git a/server/key.go b/server/key.go new file mode 100644 index 0000000000..aed1f9d1ff --- /dev/null +++ b/server/key.go @@ -0,0 +1,34 @@ +package server + +import ( + "github.com/tendermint/go-crypto/keys" + "github.com/tendermint/go-crypto/keys/words" + dbm "github.com/tendermint/tmlibs/db" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// 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() (sdk.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 +} diff --git a/server/start.go b/server/start.go index 530a3a3e4b..c8d9fc4d1d 100644 --- a/server/start.go +++ b/server/start.go @@ -29,7 +29,7 @@ type appCreator func(string, log.Logger) (abci.Application, error) // stand-alone, or in-process with tendermint func StartCmd(app appCreator, logger log.Logger) *cobra.Command { start := startCmd{ - appCreator: appCreator, + appCreator: app, logger: logger, } cmd := &cobra.Command{ diff --git a/server/start_test.go b/server/start_test.go index de396c46ff..4934e84022 100644 --- a/server/start_test.go +++ b/server/start_test.go @@ -27,8 +27,7 @@ func TestStartStandAlone(t *testing.T) { startCmd.Flags().Set(flagAddress, FreeTCPAddr(t)) // set to a new free address timeout := time.Duration(3) * time.Second - ch := RunOrTimeout(startCmd, timeout, t) - close(ch) + RunOrTimeout(startCmd, timeout, t) } func TestStartWithTendermint(t *testing.T) { @@ -50,6 +49,5 @@ func TestStartWithTendermint(t *testing.T) { //a, _ := startCmd.Flags().GetString(flagAddress) //panic(a) - ch := RunOrTimeout(startCmd, timeout, t) - close(ch) + RunOrTimeout(startCmd, timeout, t) } diff --git a/types/tx_msg.go b/types/tx_msg.go index e4af1d8040..b81d2ddf98 100644 --- a/types/tx_msg.go +++ b/types/tx_msg.go @@ -2,7 +2,6 @@ package types import ( "encoding/json" - "fmt" ) // Transactions messages must fulfill the Msg @@ -125,7 +124,6 @@ type StdSignDoc struct { // StdSignBytes returns the bytes to sign for a transaction. // TODO: change the API to just take a chainID and StdTx ? func StdSignBytes(chainID string, sequences []int64, fee StdFee, msg Msg) []byte { - fmt.Println("FEE BYTES BABY", fee, string(fee.Bytes())) bz, err := json.Marshal(StdSignDoc{ ChainID: chainID, Sequences: sequences, diff --git a/x/auth/ante.go b/x/auth/ante.go index 08bb185778..c9509251dc 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -5,6 +5,7 @@ import ( "fmt" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/spf13/viper" ) // NewAnteHandler returns an AnteHandler that checks @@ -46,6 +47,12 @@ func NewAnteHandler(accountMapper sdk.AccountMapper) sdk.AnteHandler { sequences[i] = sigs[i].Sequence } fee := stdTx.Fee + chainID := ctx.ChainID() + // XXX: major hack; need to get ChainID + // into the app right away (#565) + if chainID == "" { + chainID = viper.GetString("chain-id") + } signBytes := sdk.StdSignBytes(ctx.ChainID(), sequences, fee, msg) // Check sig and nonce and collect signer accounts. diff --git a/x/bank/rest/sendtx.go b/x/bank/rest/sendtx.go index 4b1c8f12a0..85b9dc4d50 100644 --- a/x/bank/rest/sendtx.go +++ b/x/bank/rest/sendtx.go @@ -7,10 +7,11 @@ import ( "net/http" "github.com/gorilla/mux" + "github.com/spf13/viper" "github.com/tendermint/go-crypto/keys" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/builder" - "github.com/cosmos/cosmos-sdk/client/keys" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" "github.com/cosmos/cosmos-sdk/x/bank/commands" @@ -72,6 +73,8 @@ func SendRequestHandler(cdc *wire.Codec, kb keys.Keybase) func(http.ResponseWrit } // sign + // XXX: OMG + viper.Set(client.FlagSequence, m.Sequence) txBytes, err := builder.SignAndBuild(m.LocalAccountName, m.Password, msg, c.Cdc) if err != nil { w.WriteHeader(http.StatusUnauthorized) From 1491de4522cba07d267f909bc97c991fa3bca2ea Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 18 Mar 2018 02:03:25 +0100 Subject: [PATCH 64/64] types/errors_test.go --- types/errors_test.go | 53 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 types/errors_test.go diff --git a/types/errors_test.go b/types/errors_test.go new file mode 100644 index 0000000000..7ca78e7265 --- /dev/null +++ b/types/errors_test.go @@ -0,0 +1,53 @@ +package types + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/assert" +) + +var codeTypes = []CodeType{ + CodeInternal, + CodeTxParse, + CodeInvalidSequence, + CodeUnauthorized, + CodeInsufficientFunds, + CodeUnknownRequest, + CodeUnrecognizedAddress, + CodeInvalidPubKey, + CodeGenesisParse, +} + +type errFn func(msg string) Error + +var errFns = []errFn{ + ErrInternal, + ErrTxParse, + ErrInvalidSequence, + ErrUnauthorized, + ErrInsufficientFunds, + ErrUnknownRequest, + ErrUnrecognizedAddress, + ErrInvalidPubKey, + ErrGenesisParse, +} + +func TestCodeType(t *testing.T) { + assert.True(t, CodeOK.IsOK()) + + for _, c := range codeTypes { + assert.False(t, c.IsOK()) + msg := CodeToDefaultMsg(c) + assert.False(t, strings.HasPrefix(msg, "Unknown code")) + } +} + +func TestErrFn(t *testing.T) { + for i, errFn := range errFns { + err := errFn("") + codeType := codeTypes[i] + assert.Equal(t, err.ABCICode(), codeType) + assert.Equal(t, err.Result().Code, codeType) + } +}