Merge PR #1682: Docs example app tests
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
+16
-18
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user