fixes from review
This commit is contained in:
parent
3a96f8ffd1
commit
e8946e9b36
|
@ -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!
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue