simplify and complete app3
This commit is contained in:
parent
d1a42e0691
commit
1d4d9e922f
|
@ -15,14 +15,13 @@ working with accounts in the store.
|
|||
The `x/bank` module implements `Msg` and `Handler` - it has everything we need
|
||||
to transfer coins between accounts.
|
||||
|
||||
Applications that use `x/auth` and `x/bank` thus significantly reduce the amount
|
||||
of work they have to do so they can focus on their application specific logic in
|
||||
their own modules.
|
||||
Here, we'll introduce the important types from `x/auth` and `x/bank`, and use
|
||||
them to build `App3`, our shortest app yet. The complete code can be found in
|
||||
[app3.go](examples/app3.go), and at the end of this section.
|
||||
|
||||
Here, we'll introduce the important types from `x/auth` and `x/bank`, and show
|
||||
how to make `App3` by using them. The complete code can be found in [app3.go](examples/app3.go).
|
||||
For more details, see the
|
||||
[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and [x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation.
|
||||
[x/auth](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth) and
|
||||
[x/bank](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank) API documentation.
|
||||
|
||||
## Accounts
|
||||
|
||||
|
@ -260,8 +259,8 @@ the same message could be executed over and over again.
|
|||
The PubKey is required for signature verification, but it is only required in
|
||||
the StdSignature once. From that point on, it will be stored in the account.
|
||||
|
||||
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`.
|
||||
The convenience function `FeePayer(tx Tx) sdk.Address` is provided to return this.
|
||||
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`,
|
||||
as provided by the `FeePayer(tx Tx) sdk.Address` function.
|
||||
|
||||
## CoinKeeper
|
||||
|
||||
|
@ -269,10 +268,10 @@ Now that we've seen the `auth.AccountMapper` and how its used to build a
|
|||
complete AnteHandler, it's time to look at how to build higher-level
|
||||
abstractions for taking action on accounts.
|
||||
|
||||
Earlier, we noted that `Mappers` abstactions over a KVStore that handles marshalling and unmarshalling a
|
||||
particular data type to and from the underlying store. We can build another
|
||||
abstraction on top of `Mappers` that we call `Keepers`, which expose only
|
||||
limitted functionality on the underlying types stored by the `Mapper`.
|
||||
Earlier, we noted that `Mappers` are abstactions over KVStores that handle
|
||||
marshalling and unmarshalling data types to and from underlying stores.
|
||||
We can build another abstraction on top of `Mappers` that we call `Keepers`,
|
||||
which expose only limitted functionality on the underlying types stored by the `Mapper`.
|
||||
|
||||
For instance, the `x/bank` module defines the canonical versions of `MsgSend`
|
||||
and `MsgIssue` for the SDK, as well as a `Handler` for processing them. However,
|
||||
|
@ -303,23 +302,69 @@ See the [bank.Keeper API
|
|||
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full set of methods.
|
||||
|
||||
Note we can refine the `bank.Keeper` by restricting it's method set. For
|
||||
instance, the `bank.ViewKeeper` is a read-only version, while the
|
||||
`bank.SendKeeper` only executes transfers of coins from input accounts to output
|
||||
instance, the
|
||||
[bank.ViewKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#ViewKeeper)
|
||||
is a read-only version, while the
|
||||
[bank.SendKeeper](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#SendKeeper)
|
||||
only executes transfers of coins from input accounts to output
|
||||
accounts.
|
||||
|
||||
We use this `Keeper` paradigm extensively in the SDK as the way to define what
|
||||
kind of functionality each module gets access to. In particular, we try to
|
||||
follow the *principle of least authority*, where modules only get access to the
|
||||
absolutely narrowest set of functionality they need to get the job done. Hence,
|
||||
rather than providing full blown access to the `KVStore` or the `AccountMapper`,
|
||||
follow the *principle of least authority*.
|
||||
Rather than providing full blown access to the `KVStore` or the `AccountMapper`,
|
||||
we restrict access to a small number of functions that do very specific things.
|
||||
|
||||
## App3
|
||||
|
||||
Armed with an understanding of mappers and keepers, in particular the
|
||||
`auth.AccountMapper` and the `bank.Keeper`, we're now ready to build `App3`
|
||||
using the `x/auth` and `x/bank` modules to do all the heavy lifting:
|
||||
With the `auth.AccountMapper` and `bank.Keeper` in hand,
|
||||
we're now ready to build `App3`.
|
||||
The `x/auth` and `x/bank` modules do all the heavy lifting:
|
||||
|
||||
```go
|
||||
TODO
|
||||
func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
||||
|
||||
// Create the codec with registered Msg types
|
||||
cdc := NewCodec()
|
||||
|
||||
// Create the base application object.
|
||||
app := bapp.NewBaseApp(app3Name, cdc, logger, db)
|
||||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
```
|
||||
|
||||
Note we use `bank.NewHandler`, which handles only `bank.MsgSend`,
|
||||
and receives only the `bank.Keeper`. See the
|
||||
[x/bank API docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank)
|
||||
for more details.
|
||||
|
||||
## Conclusion
|
||||
|
||||
Armed with native modules for authentication and coin transfer,
|
||||
emboldened by the paradigm of mappers and keepers,
|
||||
and ever invigorated by the desire to build secure state-machines,
|
||||
we find ourselves here with a full-blown, all-checks-in-place, multi-asset
|
||||
cryptocurrency - the beating heart of the Cosmos-SDK.
|
||||
|
|
|
@ -1,10 +1,6 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
cmn "github.com/tendermint/tmlibs/common"
|
||||
dbm "github.com/tendermint/tmlibs/db"
|
||||
"github.com/tendermint/tmlibs/log"
|
||||
|
@ -29,132 +25,25 @@ func NewApp3(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
|||
|
||||
// Create a key for accessing the account store.
|
||||
keyAccount := sdk.NewKVStoreKey("acc")
|
||||
keyMain := sdk.NewKVStoreKey("main")
|
||||
keyFees := sdk.NewKVStoreKey("fee")
|
||||
keyFees := sdk.NewKVStoreKey("fee") // TODO
|
||||
|
||||
// Set various mappers/keepers to interact easily with underlying stores
|
||||
// TODO: Need to register Account interface or use different Codec
|
||||
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
|
||||
coinKeeper := bank.NewKeeper(accountMapper)
|
||||
infoMapper := newCoinInfoMapper(keyMain)
|
||||
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)
|
||||
|
||||
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
|
||||
|
||||
// Register message routes.
|
||||
// Note the handler gets access to the account store.
|
||||
// Note the handler gets access to
|
||||
app.Router().
|
||||
AddRoute("send", handleMsgSendWithKeeper(coinKeeper)).
|
||||
AddRoute("issue", handleMsgIssueWithInfoMapper(infoMapper, coinKeeper))
|
||||
AddRoute("send", bank.NewHandler(coinKeeper))
|
||||
|
||||
// Mount stores and load the latest state.
|
||||
app.MountStoresIAVL(keyAccount, keyMain, keyFees)
|
||||
app.MountStoresIAVL(keyAccount, keyFees)
|
||||
err := app.LoadLatestVersion(keyAccount)
|
||||
if err != nil {
|
||||
cmn.Exit(err.Error())
|
||||
}
|
||||
return app
|
||||
}
|
||||
|
||||
func handleMsgSendWithKeeper(coinKeeper bank.Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
sendMsg, ok := msg.(MsgSend)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "Send Message Malformed").Result()
|
||||
}
|
||||
|
||||
// Subtract coins from sender account
|
||||
_, _, err := coinKeeper.SubtractCoins(ctx, sendMsg.From, sendMsg.Amount)
|
||||
if err != nil {
|
||||
// if error, return its result
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
// Add coins to receiver account
|
||||
_, _, err = coinKeeper.AddCoins(ctx, sendMsg.To, sendMsg.Amount)
|
||||
if err != nil {
|
||||
// if error, return its result
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Tags: sendMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleMsgIssueWithInfoMapper(infoMapper coinInfoMapper, coinKeeper bank.Keeper) sdk.Handler {
|
||||
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
||||
issueMsg, ok := msg.(MsgIssue)
|
||||
if !ok {
|
||||
return sdk.NewError(2, 1, "Issue Message Malformed").Result()
|
||||
}
|
||||
|
||||
// Handle updating metadata
|
||||
if res := handleCoinInfoWithMapper(ctx, infoMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
||||
return res
|
||||
}
|
||||
|
||||
// Add newly issued coins to output address
|
||||
_, _, err := coinKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin})
|
||||
if err != nil {
|
||||
return err.Result()
|
||||
}
|
||||
|
||||
return sdk.Result{
|
||||
Tags: issueMsg.Tags(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleCoinInfoWithMapper(ctx sdk.Context, infoMapper CoinInfoMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result {
|
||||
coinInfo := infoMapper.GetInfo(ctx, coin.Denom)
|
||||
|
||||
// Metadata was created fresh, should set issuer to msg issuer
|
||||
if len(coinInfo.Issuer) == 0 {
|
||||
coinInfo.Issuer = issuer
|
||||
}
|
||||
|
||||
// Msg Issuer is not authorized to issue these coins
|
||||
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
||||
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
||||
}
|
||||
|
||||
return sdk.Result{}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Mapper for CoinInfo
|
||||
|
||||
// Example of a very simple user-defined read-only mapper interface.
|
||||
type CoinInfoMapper interface {
|
||||
GetInfo(sdk.Context, string) coinInfo
|
||||
}
|
||||
|
||||
// Implements CoinInfoMapper.
|
||||
type coinInfoMapper struct {
|
||||
key *sdk.KVStoreKey
|
||||
}
|
||||
|
||||
// Construct new CoinInfoMapper.
|
||||
func newCoinInfoMapper(key *sdk.KVStoreKey) coinInfoMapper {
|
||||
return coinInfoMapper{key: key}
|
||||
}
|
||||
|
||||
// Implements CoinInfoMapper. Returns info for coin.
|
||||
func (cim coinInfoMapper) GetInfo(ctx sdk.Context, denom string) coinInfo {
|
||||
store := ctx.KVStore(cim.key)
|
||||
|
||||
infoBytes := store.Get([]byte(denom))
|
||||
if infoBytes == nil {
|
||||
// TODO
|
||||
}
|
||||
|
||||
var coinInfo coinInfo
|
||||
err := json.Unmarshal(infoBytes, &coinInfo)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return coinInfo
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue