fixes from review

This commit is contained in:
Ethan Buchman 2018-06-28 19:06:10 -04:00
parent 3a96f8ffd1
commit e8946e9b36
8 changed files with 62 additions and 35 deletions

View File

@ -18,15 +18,15 @@ type Msg interface {
// Must be alphanumeric or empty. // Must be alphanumeric or empty.
// Must correspond to name of message handler (XXX). // Must correspond to name of message handler (XXX).
Type() string Type() string
// ValidateBasic does a simple validation check that
// doesn't require access to any other information.
ValidateBasic() error
// Get the canonical byte representation of the Msg. // Get the canonical byte representation of the Msg.
// This is what is signed. // This is what is signed.
GetSignBytes() []byte GetSignBytes() []byte
// ValidateBasic does a simple validation check that
// doesn't require access to any other information.
ValidateBasic() error
// Signers returns the addrs of signers that must sign. // Signers returns the addrs of signers that must sign.
// CONTRACT: All signatures must be present to be valid. // CONTRACT: All signatures must be present to be valid.
// CONTRACT: Returns addrs in some deterministic order. // CONTRACT: Returns addrs in some deterministic order.
@ -38,10 +38,6 @@ type Msg interface {
The `Msg` interface allows messages to define basic validity checks, as well as The `Msg` interface allows messages to define basic validity checks, as well as
what needs to be signed and who needs to sign it. what needs to be signed and who needs to sign it.
Addresses in the SDK are arbitrary byte arrays that are hex-encoded when
displayed as a string or rendered in JSON. Typically, addresses are the hash of
a public key.
For instance, take the simple token sending message type from app1.go: For instance, take the simple token sending message type from app1.go:
```go ```go
@ -74,8 +70,14 @@ func (msg MsgSend) GetSigners() []sdk.Address {
} }
``` ```
Note Addresses in the SDK are arbitrary byte arrays that are [Bech32](TODO) encoded
when displayed as a string or rendered in JSON. Typically, addresses are the hash of
a public key, so we can use them to uniquely identify the required signers for a
transaction.
The basic validity check ensures the From and To address are specified and the The basic validity check ensures the From and To address are specified and the
amount is positive: Amount is positive:
```go ```go
// Implements Msg. Ensure the addresses are good and the // Implements Msg. Ensure the addresses are good and the
@ -94,6 +96,8 @@ func (msg MsgSend) ValidateBasic() sdk.Error {
} }
``` ```
Note the `ValidateBasic` method is called automatically by the SDK!
## KVStore ## KVStore
The basic persistence layer for an SDK application is the KVStore: The basic persistence layer for an SDK application is the KVStore:
@ -240,8 +244,10 @@ func NewApp1Handler(keyAcc *sdk.KVStoreKey) sdk.Handler {
We have only a single message type, so just one message-specific function to define, `handleMsgSend`. We have only a single message type, so just one message-specific function to define, `handleMsgSend`.
Note this handler has unfettered access to the store specified by the capability key `keyAcc`. So it must also define items in the store are encoded. Note this handler has unrestricted access to the store specified by the capability key `keyAcc`,
For this first example, we will define a simple account that is JSON encoded: so it must define what to store and how to encode it. Later, we'll introduce
higher-level abstractions so Handlers are restricted in what they can do.
For this first example, we use a simple account that is JSON encoded:
```go ```go
type appAccount struct { type appAccount struct {
@ -249,7 +255,8 @@ type appAccount struct {
} }
``` ```
Coins is a useful type provided by the SDK for multi-asset accounts. While we could just use an integer here for a single coin type, Coins is a useful type provided by the SDK for multi-asset accounts.
We could just use an integer here for a single coin type, but
it's worth [getting to know Coins](TODO). it's worth [getting to know Coins](TODO).
@ -258,6 +265,7 @@ Now we're ready to handle the MsgSend:
```go ```go
// Handle MsgSend. // Handle MsgSend.
// NOTE: msg.From, msg.To, and msg.Amount were already validated // NOTE: msg.From, msg.To, and msg.Amount were already validated
// in ValidateBasic().
func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result { func handleMsgSend(ctx sdk.Context, key *sdk.KVStoreKey, msg MsgSend) sdk.Result {
// Load the store. // Load the store.
store := ctx.KVStore(key) store := ctx.KVStore(key)
@ -359,7 +367,7 @@ func handleTo(store sdk.KVStore, to sdk.Address, amt sdk.Coins) sdk.Result {
And that's that! And that's that!
# Tx ## Tx
The final piece before putting it all together is the `Tx`. The final piece before putting it all together is the `Tx`.
While `Msg` contains the content for particular functionality in the application, the actual input While `Msg` contains the content for particular functionality in the application, the actual input
@ -408,7 +416,7 @@ func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
} }
``` ```
# BaseApp ## BaseApp
Finally, we stitch it all together using the `BaseApp`. Finally, we stitch it all together using the `BaseApp`.
@ -467,6 +475,25 @@ After setting the transaction decoder and the message handling routes, the final
step is to mount the stores and load the latest version. step is to mount the stores and load the latest version.
Since we only have one store, we only mount one. Since we only have one store, we only mount one.
## Execution
We're now done the core logic of the app! From here, we could write transactions
in Go and execute them against the application using the `app.DeliverTx` method.
In a real setup, the app would run as an ABCI application and
would be driven by blocks of transactions from the Tendermint consensus engine.
Later in the tutorial, we'll connect our app to a complete suite of components
for running and using a live blockchain application. For complete details on
how ABCI applications work, see the [ABCI documentation](TODO).
For now, we note the follow sequence of events occurs when a transaction is
received (through `app.DeliverTx`):
- serialized transaction is received by `app.DeliverTx`
- transaction is deserialized using `TxDecoder`
- for each message in the transaction, run `msg.ValidateBasic()`
- for each message in the transaction, load the appropriate handler and execute
it with the message
## Conclusion ## Conclusion
We now have a complete implementation of a simple app! We now have a complete implementation of a simple app!

View File

@ -84,10 +84,10 @@ Now that we're using Amino, we can embed the `Msg` interface directly in our
```go ```go
// Simple tx to wrap the Msg. // Simple tx to wrap the Msg.
type app2Tx struct { type app2Tx struct {
sdk.Msg sdk.Msg
PubKey crypto.PubKey PubKey crypto.PubKey
Signature crypto.Signature Signature crypto.Signature
} }
// This tx only has one Msg. // This tx only has one Msg.

View File

@ -113,9 +113,9 @@ Then we can get, modify, and set accounts. For instance, we could double the
amount of coins in an account: amount of coins in an account:
```go ```go
acc := GetAccount(ctx, addr) acc := accountMapper.GetAccount(ctx, addr)
acc.SetCoins(acc.Coins.Plus(acc.Coins)) acc.SetCoins(acc.Coins.Plus(acc.Coins))
acc.SetAccount(ctx, addr) accountMapper.SetAccount(ctx, addr)
``` ```
Note that the `AccountMapper` takes a `Context` as the first argument, and will Note that the `AccountMapper` takes a `Context` as the first argument, and will

View File

@ -106,6 +106,7 @@ func (msg MsgSend) Tags() sdk.Tags {
// Handle MsgSend. // Handle MsgSend.
// NOTE: msg.From, msg.To, and msg.Amount were already validated // NOTE: msg.From, msg.To, and msg.Amount were already validated
// in ValidateBasic().
func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler { func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler {
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
sendMsg, ok := msg.(MsgSend) sendMsg, ok := msg.(MsgSend)
@ -115,7 +116,6 @@ func handleMsgSend(key *sdk.KVStoreKey) sdk.Handler {
return sdk.NewError(2, 1, "Send Message is malformed").Result() return sdk.NewError(2, 1, "Send Message is malformed").Result()
} }
// Load the store. // Load the store.
store := ctx.KVStore(key) store := ctx.KVStore(key)

View File

@ -1,9 +1,9 @@
package app package app
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
@ -76,9 +76,9 @@ type CoinMetadata struct {
// if he is the issuer in Coin Metadata // if he is the issuer in Coin Metadata
// Implements sdk.Msg Interface // Implements sdk.Msg Interface
type MsgIssue struct { type MsgIssue struct {
Issuer sdk.Address Issuer sdk.Address
Receiver sdk.Address Receiver sdk.Address
Coin sdk.Coin Coin sdk.Coin
} }
// Implements Msg. // Implements Msg.
@ -179,7 +179,7 @@ func handleMetaData(store sdk.KVStore, issuer sdk.Address, coin sdk.Coin) sdk.Re
} }
// Msg Issuer is not authorized to issue these coins // Msg Issuer is not authorized to issue these coins
if !reflect.DeepEqual(metadata.Issuer, issuer) { if !bytes.Equal(metadata.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
} }
@ -198,11 +198,10 @@ func handleMetaData(store sdk.KVStore, issuer sdk.Address, coin sdk.Coin) sdk.Re
// Update store with new metadata // Update store with new metadata
store.Set([]byte(coin.Denom), val) store.Set([]byte(coin.Denom), val)
return sdk.Result{} return sdk.Result{}
} }
//------------------------------------------------------------------ //------------------------------------------------------------------
// Tx // Tx
@ -246,7 +245,7 @@ func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort
sig := appTx.GetSignatures()[i] sig := appTx.GetSignatures()[i]
// check that submitted pubkey belongs to required address // check that submitted pubkey belongs to required address
if !reflect.DeepEqual(sig.PubKey.Address(), addr) { if !bytes.Equal(sig.PubKey.Address(), addr) {
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
} }

View File

@ -1,8 +1,8 @@
package app package app
import ( import (
"bytes"
"encoding/json" "encoding/json"
"reflect"
"fmt" "fmt"
cmn "github.com/tendermint/tmlibs/common" cmn "github.com/tendermint/tmlibs/common"
@ -94,13 +94,13 @@ func betterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper bank.Keep
if res := betterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() { if res := betterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
return res return res
} }
// Add newly issued coins to output address // Add newly issued coins to output address
_, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}) _, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin})
if err != nil { if err != nil {
return err.Result() return err.Result()
} }
return sdk.Result{ return sdk.Result{
// Return result with Issue msg tags // Return result with Issue msg tags
Tags: issueMsg.Tags(), Tags: issueMsg.Tags(),
@ -117,7 +117,7 @@ func betterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer
} }
// Msg Issuer is not authorized to issue these coins // Msg Issuer is not authorized to issue these coins
if !reflect.DeepEqual(metadata.Issuer, issuer) { if !bytes.Equal(metadata.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"reflect" "reflect"
@ -172,7 +173,7 @@ func evenBetterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, is
} }
// Msg Issuer not authorized to issue these coins // Msg Issuer not authorized to issue these coins
if !reflect.DeepEqual(metadata.Issuer, issuer) { if !bytes.Equal(metadata.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result() return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
} }

View File

@ -11,13 +11,13 @@ type Msg interface {
// Must be alphanumeric or empty. // Must be alphanumeric or empty.
Type() string Type() string
// Get the canonical byte representation of the Msg.
GetSignBytes() []byte
// ValidateBasic does a simple validation check that // ValidateBasic does a simple validation check that
// doesn't require access to any other information. // doesn't require access to any other information.
ValidateBasic() Error ValidateBasic() Error
// Get the canonical byte representation of the Msg.
GetSignBytes() []byte
// Signers returns the addrs of signers that must sign. // Signers returns the addrs of signers that must sign.
// CONTRACT: All signatures must be present to be valid. // CONTRACT: All signatures must be present to be valid.
// CONTRACT: Returns addrs in some deterministic order. // CONTRACT: Returns addrs in some deterministic order.