Merge PR #1682: Docs example app tests

This commit is contained in:
Aditya 2018-07-23 21:13:55 -07:00 committed by Christopher Goes
parent a19cc0fb7e
commit 671d8494b4
8 changed files with 280 additions and 47 deletions

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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")
}

View File

@ -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
}

View File

@ -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)

View File

@ -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)
}