From 671d8494b4df96276053e700778836943012f43d Mon Sep 17 00:00:00 2001 From: Aditya Date: Mon, 23 Jul 2018 21:13:55 -0700 Subject: [PATCH] Merge PR #1682: Docs example app tests --- PENDING.md | 1 + docs/sdk/core/app1.md | 8 +- docs/sdk/core/app2.md | 34 ++++--- docs/sdk/core/examples/app2.go | 38 ++++---- docs/sdk/core/examples/app2_test.go | 83 ++++++++++++++++ docs/sdk/core/examples/app3.go | 17 +++- docs/sdk/core/examples/app4.go | 4 +- docs/sdk/core/examples/app4_test.go | 142 ++++++++++++++++++++++++++++ 8 files changed, 280 insertions(+), 47 deletions(-) create mode 100644 docs/sdk/core/examples/app2_test.go create mode 100644 docs/sdk/core/examples/app4_test.go diff --git a/PENDING.md b/PENDING.md index 49ceaf94c..5fa54eab2 100644 --- a/PENDING.md +++ b/PENDING.md @@ -37,6 +37,7 @@ IMPROVEMENTS * [tools] Remove `rm -rf vendor/` from `make get_vendor_deps` * [x/auth] Recover ErrorOutOfGas panic in order to set sdk.Result attributes correctly * [x/stake] Add revoked to human-readable validator +* [tests] Add tests to example apps in docs * [x/gov] Votes on a proposal can now be queried * [x/bank] Unit tests are now table-driven diff --git a/docs/sdk/core/app1.md b/docs/sdk/core/app1.md index ddf2a533e..54121d05b 100644 --- a/docs/sdk/core/app1.md +++ b/docs/sdk/core/app1.md @@ -27,7 +27,7 @@ type Msg interface { // This is what is signed. GetSignBytes() []byte - // Signers returns the addrs of signers that must sign. + // GetSigners returns the addrs of signers that must sign. // CONTRACT: All signatures must be present to be valid. // CONTRACT: Returns addrs in some deterministic order. GetSigners() []AccAddress @@ -49,7 +49,7 @@ type MsgSend struct { } // Implements Msg. -func (msg MsgSend) Type() string { return "bank" } +func (msg MsgSend) Type() string { return "send" } ``` It specifies that the message should be JSON marshaled and signed by the sender: @@ -432,7 +432,7 @@ func NewApp1(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Note the handler receives the keyAccount and thus // gets access to the account store. app.Router(). - AddRoute("bank", NewApp1Handler(keyAccount)) + AddRoute("send", NewApp1Handler(keyAccount)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount) @@ -450,7 +450,7 @@ We'll talk about how to connect this app object with the CLI, a REST API, the logger, and the filesystem later in the tutorial. For now, note that this is where we register handlers for messages and grant them access to stores. -Here, we have only a single Msg type, `bank`, a single store for accounts, and a single handler. +Here, we have only a single Msg type, `send`, a single store for accounts, and a single handler. The handler is granted access to the store by giving it the capability key. In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store. diff --git a/docs/sdk/core/app2.md b/docs/sdk/core/app2.md index 1f9e81a31..3de7f7b8d 100644 --- a/docs/sdk/core/app2.md +++ b/docs/sdk/core/app2.md @@ -1,6 +1,6 @@ # Transactions -In the previous app we built a simple `bank` with one message type for sending +In the previous app we built a simple bank with one message type `send` for sending coins and one store for storing accounts. Here we build `App2`, which expands on `App1` by introducing @@ -144,10 +144,14 @@ func NewCodec() *wire.Codec { cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + crypto.RegisterAmino(cdc) return cdc } ``` +Note: We also register the types in the `tendermint/tendermint/crypto` module so that `crypto.PubKey` +and `crypto.Signature` are encoded/decoded correctly. + Amino supports encoding and decoding in both a binary and JSON format. See the [codec API docs](https://godoc.org/github.com/tendermint/go-amino#Codec) for more details. @@ -219,28 +223,22 @@ func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true } - // expect only one msg in app2Tx + // expect only one msg and one signer in app2Tx msg := tx.GetMsgs()[0] - - signerAddrs := msg.GetSigners() - - if len(signerAddrs) != len(appTx.GetSignatures()) { - return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true - } + signerAddr := msg.GetSigners()[0] signBytes := msg.GetSignBytes() - for i, addr := range signerAddrs { - sig := appTx.GetSignatures()[i] - // check that submitted pubkey belongs to required address - if !bytes.Equal(sig.PubKey.Address(), addr) { - return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true - } + sig := appTx.GetSignature() - // check that signature is over expected signBytes - if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { - return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true - } + // check that submitted pubkey belongs to required address + if !bytes.Equal(appTx.PubKey.Address(), signerAddr) { + return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true + } + + // check that signature is over expected signBytes + if !appTx.PubKey.VerifyBytes(signBytes, sig) { + return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true } // authentication passed, app to continue processing by sending msg to handler diff --git a/docs/sdk/core/examples/app2.go b/docs/sdk/core/examples/app2.go index 1e1910b62..dcc05a390 100644 --- a/docs/sdk/core/examples/app2.go +++ b/docs/sdk/core/examples/app2.go @@ -13,7 +13,6 @@ import ( bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/wire" - "github.com/cosmos/cosmos-sdk/x/auth" ) const ( @@ -29,6 +28,7 @@ func NewCodec() *wire.Codec { cdc.RegisterInterface((*sdk.Msg)(nil), nil) cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + crypto.RegisterAmino(cdc) return cdc } @@ -179,7 +179,9 @@ type coinInfo struct { // Simple tx to wrap the Msg. type app2Tx struct { sdk.Msg - Signatures []auth.StdSignature + + PubKey crypto.PubKey + Signature crypto.Signature } // This tx only has one Msg. @@ -187,8 +189,8 @@ func (tx app2Tx) GetMsgs() []sdk.Msg { return []sdk.Msg{tx.Msg} } -func (tx app2Tx) GetSignatures() []auth.StdSignature { - return tx.Signatures +func (tx app2Tx) GetSignature() crypto.Signature { + return tx.Signature } // Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue @@ -214,28 +216,22 @@ func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true } - // expect only one msg in app2Tx + // expect only one msg and one signer in app2Tx msg := tx.GetMsgs()[0] - - signerAddrs := msg.GetSigners() - - if len(signerAddrs) != len(appTx.GetSignatures()) { - return ctx, sdk.ErrUnauthorized("Number of signatures do not match required amount").Result(), true - } + signerAddr := msg.GetSigners()[0] signBytes := msg.GetSignBytes() - for i, addr := range signerAddrs { - sig := appTx.GetSignatures()[i] - // check that submitted pubkey belongs to required address - if !bytes.Equal(sig.PubKey.Address(), addr) { - return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true - } + sig := appTx.GetSignature() - // check that signature is over expected signBytes - if !sig.PubKey.VerifyBytes(signBytes, sig.Signature) { - return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true - } + // check that submitted pubkey belongs to required address + if !bytes.Equal(appTx.PubKey.Address(), signerAddr) { + return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true + } + + // check that signature is over expected signBytes + if !appTx.PubKey.VerifyBytes(signBytes, sig) { + return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true } // authentication passed, app to continue processing by sending msg to handler diff --git a/docs/sdk/core/examples/app2_test.go b/docs/sdk/core/examples/app2_test.go new file mode 100644 index 000000000..58a71eb47 --- /dev/null +++ b/docs/sdk/core/examples/app2_test.go @@ -0,0 +1,83 @@ +package app + +import ( + "testing" + "github.com/tendermint/tendermint/crypto" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/stretchr/testify/require" +) + +// Test encoding of app2Tx is correct with both msg types +func TestEncoding(t *testing.T) { + // Create privkeys and addresses + priv1 := crypto.GenPrivKeyEd25519() + priv2 := crypto.GenPrivKeyEd25519() + addr1 := priv1.PubKey().Address().Bytes() + addr2 := priv2.PubKey().Address().Bytes() + + sendMsg := MsgSend{ + From: addr1, + To: addr2, + Amount: sdk.Coins{{"testCoins", sdk.NewInt(100)}}, + } + + // Construct transaction + signBytes := sendMsg.GetSignBytes() + sig, err := priv1.Sign(signBytes) + if err != nil { + panic(err) + } + + sendTxBefore := app2Tx{ + Msg: sendMsg, + PubKey: priv1.PubKey(), + Signature: sig, + } + + cdc := NewCodec() + testTxDecoder := tx2Decoder(cdc) + + encodedSendTx, err := cdc.MarshalBinary(sendTxBefore) + + require.Nil(t, err, "Error encoding sendTx") + + var tx1 sdk.Tx + tx1, err = testTxDecoder(encodedSendTx) + require.Nil(t, err, "Error decoding sendTx") + + sendTxAfter := tx1.(app2Tx) + + require.Equal(t, sendTxBefore, sendTxAfter, "Transaction changed after encoding/decoding") + + issueMsg := MsgIssue{ + Issuer: addr1, + Receiver: addr2, + Coin: sdk.Coin{"testCoin", sdk.NewInt(100)}, + } + + signBytes = issueMsg.GetSignBytes() + sig, err = priv1.Sign(signBytes) + if err != nil { + panic(err) + } + + issueTxBefore := app2Tx{ + Msg: issueMsg, + PubKey: priv1.PubKey(), + Signature: sig, + } + + encodedIssueTx, err2 := cdc.MarshalBinary(issueTxBefore) + + require.Nil(t, err2, "Error encoding issueTx") + + var tx2 sdk.Tx + tx2, err2 = testTxDecoder(encodedIssueTx) + require.Nil(t, err2, "Error decoding issue Tx") + + issueTxAfter := tx2.(app2Tx) + + require.Equal(t, issueTxBefore, issueTxAfter, "Transaction changed after encoding/decoding") + +} \ No newline at end of file diff --git a/docs/sdk/core/examples/app3.go b/docs/sdk/core/examples/app3.go index 15c8ea758..009d22300 100644 --- a/docs/sdk/core/examples/app3.go +++ b/docs/sdk/core/examples/app3.go @@ -4,11 +4,13 @@ import ( cmn "github.com/tendermint/tendermint/libs/common" dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + "github.com/tendermint/tendermint/crypto" bapp "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/wire" ) const ( @@ -18,7 +20,7 @@ const ( func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Create the codec with registered Msg types - cdc := NewCodec() + cdc := UpdatedCodec() // Create the base application object. app := bapp.NewBaseApp(app3Name, logger, db, auth.DefaultTxDecoder(cdc)) @@ -37,7 +39,7 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to app.Router(). - AddRoute("send", bank.NewHandler(coinKeeper)) + AddRoute("bank", bank.NewHandler(coinKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyFees) @@ -47,3 +49,14 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp { } return app } + +// Update codec from app2 to register imported modules +func UpdatedCodec() *wire.Codec { + cdc := wire.NewCodec() + cdc.RegisterInterface((*sdk.Msg)(nil), nil) + cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil) + cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil) + auth.RegisterWire(cdc) + crypto.RegisterAmino(cdc) + return cdc +} diff --git a/docs/sdk/core/examples/app4.go b/docs/sdk/core/examples/app4.go index f8175fe41..a4432b3da 100644 --- a/docs/sdk/core/examples/app4.go +++ b/docs/sdk/core/examples/app4.go @@ -19,7 +19,7 @@ const ( func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { - cdc := NewCodec() + cdc := UpdatedCodec() // Create the base application object. app := bapp.NewBaseApp(app4Name, logger, db, auth.DefaultTxDecoder(cdc)) @@ -43,7 +43,7 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { // Register message routes. // Note the handler gets access to the account store. app.Router(). - AddRoute("send", bank.NewHandler(coinKeeper)) + AddRoute("bank", bank.NewHandler(coinKeeper)) // Mount stores and load the latest state. app.MountStoresIAVL(keyAccount, keyFees) diff --git a/docs/sdk/core/examples/app4_test.go b/docs/sdk/core/examples/app4_test.go new file mode 100644 index 000000000..4b2de7a77 --- /dev/null +++ b/docs/sdk/core/examples/app4_test.go @@ -0,0 +1,142 @@ +package app + +import ( + "github.com/stretchr/testify/require" + "os" + "encoding/json" + "testing" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + bapp "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" +) + +// Create and return App4 instance +func newTestChain() *bapp.BaseApp { + logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "sdk/app") + db := dbm.NewMemDB() + return NewApp4(logger, db) +} + +// Initialize all provided addresses with 100 testCoin +func InitTestChain(bc *bapp.BaseApp, chainID string, addrs ...sdk.AccAddress) { + var accounts []*GenesisAccount + for _, addr := range addrs { + acc := GenesisAccount{ + Address: addr, + Coins: sdk.Coins{{"testCoin", sdk.NewInt(100)}}, + } + accounts = append(accounts, &acc) + } + accountState := GenesisState{accounts} + genState, err := json.Marshal(accountState) + if err != nil { + panic(err) + } + bc.InitChain(abci.RequestInitChain{ChainId: chainID, AppStateBytes: genState}) +} + +// Generate basic SpendMsg with one input and output +func GenerateSpendMsg(sender, receiver sdk.AccAddress, amount sdk.Coins) bank.MsgSend { + return bank.MsgSend{ + Inputs: []bank.Input{{sender, amount}}, + Outputs: []bank.Output{{receiver, amount}}, + } +} + +// Test spending nonexistent funds fails +func TestBadMsg(t *testing.T) { + bc := newTestChain() + + // Create privkeys and addresses + priv1 := crypto.GenPrivKeyEd25519() + priv2 := crypto.GenPrivKeyEd25519() + addr1 := priv1.PubKey().Address().Bytes() + addr2 := priv2.PubKey().Address().Bytes() + + // Attempt to spend non-existent funds + msg := GenerateSpendMsg(addr1, addr2, sdk.Coins{{"testCoin", sdk.NewInt(100)}}) + + // Construct transaction + fee := auth.StdFee{ + Gas: 1000000000000000, + Amount: sdk.Coins{{"testCoin", sdk.NewInt(0)}}, + } + signBytes := auth.StdSignBytes("test-chain", 0, 0, fee, []sdk.Msg{msg}, "") + sig, err := priv1.Sign(signBytes) + if err != nil { + panic(err) + } + sigs := []auth.StdSignature{auth.StdSignature{ + PubKey: priv1.PubKey(), + Signature: sig, + AccountNumber: 0, + Sequence: 0, + }} + + tx := auth.StdTx{ + Msgs: []sdk.Msg{msg}, + Fee: fee, + Signatures: sigs, + Memo: "", + } + + bc.BeginBlock(abci.RequestBeginBlock{Header: abci.Header{ChainID: "test-chain"}}) + + // Deliver the transaction + res := bc.Deliver(tx) + + // Check that tx failed + require.False(t, res.IsOK(), "Invalid tx passed") + +} + +func TestMsgSend(t *testing.T) { + bc := newTestChain() + + priv1 := crypto.GenPrivKeyEd25519() + priv2 := crypto.GenPrivKeyEd25519() + addr1 := priv1.PubKey().Address().Bytes() + addr2 := priv2.PubKey().Address().Bytes() + + InitTestChain(bc, "test-chain", addr1) + + // Send funds to addr2 + msg := GenerateSpendMsg(addr1, addr2, sdk.Coins{{"testCoin", sdk.NewInt(100)}}) + + fee := auth.StdFee{ + Gas: 1000000000000000, + Amount: sdk.Coins{{"testCoin", sdk.NewInt(0)}}, + } + signBytes := auth.StdSignBytes("test-chain", 0, 0, fee, []sdk.Msg{msg}, "") + sig, err := priv1.Sign(signBytes) + if err != nil { + panic(err) + } + sigs := []auth.StdSignature{auth.StdSignature{ + PubKey: priv1.PubKey(), + Signature: sig, + AccountNumber: 0, + Sequence: 0, + }} + + tx := auth.StdTx{ + Msgs: []sdk.Msg{msg}, + Fee: fee, + Signatures: sigs, + Memo: "", + } + + bc.BeginBlock(abci.RequestBeginBlock{}) + + res := bc.Deliver(tx) + + require.True(t, res.IsOK(), res.Log) + + +} \ No newline at end of file