cosmos-sdk/docs/sdk/core/app3.md

371 lines
13 KiB
Markdown
Raw Normal View History

# Modules
2018-06-16 19:24:48 -07:00
2018-06-26 15:05:12 -07:00
In the previous app, we introduced a new `Msg` type and used Amino to encode
transactions. We also introduced additional data to the `Tx`, and used a simple
`AnteHandler` to validate it.
2018-06-16 19:24:48 -07:00
Here, in `App3`, we introduce two built-in SDK modules to
replace the `Msg`, `Tx`, `Handler`, and `AnteHandler` implementations we've seen
2018-06-27 06:02:11 -07:00
so far: `x/auth` and `x/bank`.
2018-06-26 18:29:54 -07:00
2018-06-27 06:02:11 -07:00
The `x/auth` module implements `Tx` and `AnteHandler` - it has everything we need to
authenticate transactions. It also includes a new `Account` type that simplifies
working with accounts in the store.
2018-06-26 18:29:54 -07:00
The `x/bank` module implements `Msg` and `Handler` - it has everything we need
to transfer coins between accounts.
2018-06-28 20:21:43 -07:00
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.
2018-06-28 17:16:47 -07:00
For more details, see the
2018-06-28 20:21:43 -07:00
[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
The `x/auth` module defines a model of accounts much like Ethereum.
In this model, an account contains:
2018-06-26 18:29:54 -07:00
- Address for identification
- PubKey for authentication
- AccountNumber to prune empty accounts
- Sequence to prevent transaction replays
- Coins to carry a balance
2018-06-27 06:53:00 -07:00
Note that the `AccountNumber` is a unique number that is assigned when the account is
created, and the `Sequence` is incremented by one every time a transaction is
sent from the account.
### Account
The `Account` interface captures this account model with getters and setters:
2018-06-26 18:29:54 -07:00
```go
// Account is a standard account using a sequence number for replay protection
// and a pubkey for authentication.
type Account interface {
2018-07-09 02:15:36 -07:00
GetAddress() sdk.AccAddress
SetAddress(sdk.AccAddress) error // errors if already set.
2018-06-26 18:29:54 -07:00
GetPubKey() crypto.PubKey // can return nil.
SetPubKey(crypto.PubKey) error
GetAccountNumber() int64
SetAccountNumber(int64) error
GetSequence() int64
SetSequence(int64) error
GetCoins() sdk.Coins
SetCoins(sdk.Coins) error
}
```
Note this is a low-level interface - it allows any of the fields to be over
written. As we'll soon see, access can be restricted using the `Keeper`
paradigm.
### BaseAccount
2018-06-26 18:29:54 -07:00
The default implementation of `Account` is the `BaseAccount`:
```go
// BaseAccount - base account structure.
// Extend this by embedding this in your AppAccount.
// See the examples/basecoin/types/account.go for an example.
type BaseAccount struct {
2018-07-09 02:15:36 -07:00
Address sdk.AccAddress `json:"address"`
Coins sdk.Coins `json:"coins"`
PubKey crypto.PubKey `json:"public_key"`
AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"`
2018-06-26 18:29:54 -07:00
}
```
It simply contains a field for each of the methods.
2018-06-27 06:02:11 -07:00
### AccountMapper
2018-06-26 18:29:54 -07:00
2018-06-27 06:02:11 -07:00
In previous apps using our `appAccount`, we handled
marshaling/unmarshaling the account from the store ourselves, by performing
operations directly on the KVStore. But unrestricted access to a KVStore isn't really the interface we want
to work with in our applications. In the SDK, we use the term `Mapper` to refer
to an abstaction over a KVStore that handles marshalling and unmarshalling a
particular data type to and from the underlying store.
2018-06-26 18:29:54 -07:00
2018-06-27 06:02:11 -07:00
The `x/auth` module provides an `AccountMapper` that allows us to get and
set `Account` types to the store. Note the benefit of using the `Account`
interface here - developers can implement their own account type that extends
the `BaseAccount` to store additional data without requiring another lookup from
the store.
2018-06-27 06:02:11 -07:00
Creating an AccountMapper is easy - we just need to specify a codec, a
2018-06-28 17:16:47 -07:00
capability key, and a prototype of the object being encoded
2018-06-27 06:02:11 -07:00
```go
accountMapper := auth.NewAccountMapper(cdc, keyAccount, auth.ProtoBaseAccount)
2018-06-27 06:02:11 -07:00
```
2018-06-26 18:29:54 -07:00
2018-06-27 07:08:55 -07:00
Then we can get, modify, and set accounts. For instance, we could double the
amount of coins in an account:
```go
2018-06-28 16:06:10 -07:00
acc := accountMapper.GetAccount(ctx, addr)
2018-06-27 07:08:55 -07:00
acc.SetCoins(acc.Coins.Plus(acc.Coins))
2018-06-28 16:06:10 -07:00
accountMapper.SetAccount(ctx, addr)
2018-06-27 07:08:55 -07:00
```
Note that the `AccountMapper` takes a `Context` as the first argument, and will
load the KVStore from there using the capability key it was granted on creation.
Also note that you must explicitly call `SetAccount` after mutating an account
for the change to persist!
2018-06-28 17:16:47 -07:00
See the [AccountMapper API
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/auth#AccountMapper) for more information.
2018-06-27 06:02:11 -07:00
## StdTx
2018-06-16 19:24:48 -07:00
2018-06-27 06:02:11 -07:00
Now that we have a native model for accounts, it's time to introduce the native
`Tx` type, the `auth.StdTx`:
2018-06-16 19:24:48 -07:00
```go
2018-06-26 18:29:54 -07:00
// StdTx is a standard way to wrap a Msg with Fee and Signatures.
// NOTE: the first signature is the FeePayer (Signatures must not be nil).
2018-06-16 19:24:48 -07:00
type StdTx struct {
2018-06-26 18:29:54 -07:00
Msgs []sdk.Msg `json:"msg"`
2018-06-16 19:24:48 -07:00
Fee StdFee `json:"fee"`
Signatures []StdSignature `json:"signatures"`
2018-06-26 18:29:54 -07:00
Memo string `json:"memo"`
2018-06-16 19:24:48 -07:00
}
```
2018-06-27 06:02:11 -07:00
This is the standard form for a transaction in the SDK. Besides the Msgs, it
includes:
- a fee to be paid by the first signer
- replay protecting nonces in the signature
- a memo of prunable additional data
2018-06-26 18:29:54 -07:00
2018-06-27 06:02:11 -07:00
Details on how these components are validated is provided under
2018-06-27 09:45:01 -07:00
[auth.AnteHandler](#antehandler) below.
2018-06-27 06:02:11 -07:00
The standard form for signatures is `StdSignature`:
2018-06-16 19:24:48 -07:00
```go
2018-06-26 18:29:54 -07:00
// StdSignature wraps the Signature and includes counters for replay protection.
// It also includes an optional public key, which must be provided at least in
// the first transaction made by the account.
2018-06-16 19:24:48 -07:00
type StdSignature struct {
2018-06-26 18:29:54 -07:00
crypto.PubKey `json:"pub_key"` // optional
crypto.Signature `json:"signature"`
AccountNumber int64 `json:"account_number"`
Sequence int64 `json:"sequence"`
2018-06-16 19:24:48 -07:00
}
```
2018-06-27 06:53:00 -07:00
The signature includes both an `AccountNumber` and a `Sequence`.
The `Sequence` must match the one in the
corresponding account when the transaction is processed, and will increment by
one with every transaction. This prevents the same
transaction from being replayed multiple times, resolving the insecurity that
remains in App2.
The `AccountNumber` is also for replay protection - it allows accounts to be
deleted from the store when they run out of accounts. If an account receives
coins after it is deleted, the account will be re-created, with the Sequence
reset to 0, but a new AccountNumber. If it weren't for the AccountNumber, the
last sequence of transactions made by the account before it was deleted could be
replayed!
Finally, the standard form for a transaction fee is `StdFee`:
2018-06-16 19:24:48 -07:00
```go
// StdFee includes the amount of coins paid in fees and the maximum
// gas to be used by the transaction. The ratio yields an effective "gasprice",
// which must be above some miminum to be accepted into the mempool.
type StdFee struct {
Amount sdk.Coins `json:"amount"`
Gas int64 `json:"gas"`
}
```
2018-06-27 06:53:00 -07:00
The fee must be paid by the first signer. This allows us to quickly check if the
transaction fee can be paid, and reject the transaction if not.
2018-06-27 06:02:11 -07:00
## Signing
2018-06-26 18:29:54 -07:00
2018-06-27 06:53:00 -07:00
The `StdTx` supports multiple messages and multiple signers.
To sign the transaction, each signer must collect the following information:
2018-06-28 17:16:47 -07:00
- the ChainID
2018-06-27 06:53:00 -07:00
- the AccountNumber and Sequence for the given signer's account (from the
blockchain)
- the transaction fee
- the list of transaction messages
- an optional memo
Then they can compute the transaction bytes to sign using the
`auth.StdSignBytes` function:
2018-06-26 18:29:54 -07:00
```go
2018-06-27 06:53:00 -07:00
bytesToSign := StdSignBytes(chainID, accNum, accSequence, fee, msgs, memo)
2018-06-26 18:29:54 -07:00
```
2018-06-27 09:45:01 -07:00
Note these bytes are unique for each signer, as they depend on the particular
signers AccountNumber, Sequence, and optional memo. To facilitate easy
inspection before signing, the bytes are actually just a JSON encoded form of
all the relevant information.
2018-06-16 19:24:48 -07:00
## AnteHandler
2018-06-27 06:53:00 -07:00
As we saw in `App2`, we can use an `AnteHandler` to authenticate transactions
before we handle any of their internal messages. While previously we implemented
our own simple `AnteHandler`, the `x/auth` module provides a much more advanced
one that uses `AccountMapper` and works with `StdTx`:
```go
app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))
```
The AnteHandler provided by `x/auth` enforces the following rules:
- the memo must not be too big
- the right number of signatures must be provided (one for each unique signer
returned by `msg.GetSigner` for each `msg`)
- any account signing for the first-time must include a public key in the
StdSignature
- the signatures must be valid when authenticated in the same order as specified
by the messages
Note that validating
signatures requires checking that the correct account number and sequence was
used by each signer, as this information is required in the `StdSignBytes`.
2018-06-27 09:45:01 -07:00
If any of the above are not satisfied, the AnteHandelr returns an error.
2018-06-26 14:21:46 -07:00
2018-06-27 06:53:00 -07:00
If all of the above verifications pass, the AnteHandler makes the following
changes to the state:
2018-06-27 06:02:11 -07:00
2018-06-27 06:53:00 -07:00
- increment account sequence by one for all signers
2018-06-27 09:45:01 -07:00
- set the pubkey in the account for any first-time signers
- deduct the fee from the first signer's account
2018-06-27 06:02:11 -07:00
2018-06-27 06:53:00 -07:00
Recall that incrementing the `Sequence` prevents "replay attacks" where
the same message could be executed over and over again.
2018-06-27 06:02:11 -07:00
2018-06-27 06:53:00 -07:00
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.
2018-06-27 06:02:11 -07:00
2018-06-28 20:21:43 -07:00
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`,
2018-07-09 02:15:36 -07:00
as provided by the `FeePayer(tx Tx) sdk.AccAddress` function.
2018-06-27 06:02:11 -07:00
2018-06-27 07:08:55 -07:00
## CoinKeeper
2018-06-27 09:45:01 -07:00
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.
2018-06-28 20:21:43 -07:00
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`.
2018-06-27 09:45:01 -07:00
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,
rather than passing a `KVStore` or even an `AccountMapper` directly to the handler,
we introduce a `bank.Keeper`, which can only be used to transfer coins in and out of accounts.
This allows us to determine up front that the only effect the bank module's
`Handler` can have on the store is to change the amount of coins in an account -
it can't increment sequence numbers, change PubKeys, or otherwise.
2018-06-27 07:08:55 -07:00
2018-06-27 09:45:01 -07:00
A `bank.Keeper` is easily instantiated from an `AccountMapper`:
2018-06-27 07:08:55 -07:00
```go
2018-06-27 09:45:01 -07:00
coinKeeper = bank.NewKeeper(accountMapper)
2018-06-27 07:08:55 -07:00
```
2018-06-27 09:45:01 -07:00
We can then use it within a handler, instead of working directly with the
`AccountMapper`. For instance, to add coins to an account:
2018-06-27 07:08:55 -07:00
```go
2018-06-27 09:45:01 -07:00
// Finds account with addr in AccountMapper.
// Adds coins to account's coin array.
// Sets updated account in AccountMapper
2018-06-27 07:08:55 -07:00
app.coinKeeper.AddCoins(ctx, addr, coins)
```
2018-06-28 17:16:47 -07:00
See the [bank.Keeper API
docs](https://godoc.org/github.com/cosmos/cosmos-sdk/x/bank#Keeper) for the full set of methods.
2018-06-27 09:45:01 -07:00
Note we can refine the `bank.Keeper` by restricting it's method set. For
2018-06-28 20:21:43 -07:00
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
2018-06-27 09:45:01 -07:00
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
2018-06-28 20:21:43 -07:00
follow the *principle of least authority*.
Rather than providing full blown access to the `KVStore` or the `AccountMapper`,
2018-06-27 09:45:01 -07:00
we restrict access to a small number of functions that do very specific things.
2018-06-27 07:08:55 -07:00
## App3
2018-06-26 14:21:46 -07:00
2018-06-28 20:21:43 -07:00
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:
2018-06-26 14:21:46 -07:00
```go
2018-06-28 20:21:43 -07:00
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.ProtoBaseAccount)
2018-06-28 20:21:43 -07:00
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
}
2018-06-26 14:21:46 -07:00
```
2018-06-28 20:21:43 -07:00
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.