2018-06-26 19:49:13 -07:00
|
|
|
# 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
|
2018-06-26 19:49:13 -07:00
|
|
|
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
|
|
|
|
2018-06-26 19:49:13 -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
|
2018-06-26 19:49:13 -07:00
|
|
|
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
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
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 show
|
|
|
|
how to make `App3` by using them. The complete code can be found in [app3.go](examples/app3.go).
|
2018-06-27 09:45:01 -07:00
|
|
|
For more details, see the [x/auth](TODO) and [x/bank](TODO) API documentation.
|
2018-06-26 19:49:13 -07:00
|
|
|
|
|
|
|
## 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.
|
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
### 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 {
|
|
|
|
GetAddress() sdk.Address
|
|
|
|
SetAddress(sdk.Address) error // errors if already set.
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
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 {
|
|
|
|
Address sdk.Address `json:"address"`
|
|
|
|
Coins sdk.Coins `json:"coins"`
|
|
|
|
PubKey crypto.PubKey `json:"public_key"`
|
|
|
|
AccountNumber int64 `json:"account_number"`
|
|
|
|
Sequence int64 `json:"sequence"`
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
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-26 19:49:13 -07:00
|
|
|
|
2018-06-27 06:02:11 -07:00
|
|
|
Creating an AccountMapper is easy - we just need to specify a codec, a
|
|
|
|
capability key, and a prototype of the object being encoded (TODO: change to
|
|
|
|
constructor):
|
2018-06-26 19:49:13 -07:00
|
|
|
|
2018-06-27 06:02:11 -07:00
|
|
|
```go
|
|
|
|
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
|
|
|
|
```
|
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-27 09:45:01 -07:00
|
|
|
acc := GetAccount(ctx, addr)
|
2018-06-27 07:08:55 -07:00
|
|
|
acc.SetCoins(acc.Coins.Plus(acc.Coins))
|
|
|
|
acc.SetAccount(ctx, addr)
|
|
|
|
```
|
|
|
|
|
|
|
|
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-27 06:02:11 -07:00
|
|
|
See the [AccountMapper API docs](TODO) for more information.
|
2018-06-26 19:49:13 -07:00
|
|
|
|
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:
|
|
|
|
|
|
|
|
- the ChainID (TODO haven't mentioned this yet)
|
|
|
|
- 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
|
|
|
|
TODO: feekeeper :(
|
|
|
|
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-27 09:45:01 -07:00
|
|
|
The fee is paid by the first address returned by `msg.GetSigners()` for the first `Msg`.
|
2018-06-27 06:53:00 -07:00
|
|
|
The convenience function `FeePayer(tx Tx) sdk.Address` is provided to return this.
|
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.
|
|
|
|
|
|
|
|
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`.
|
|
|
|
|
|
|
|
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-27 09:45:01 -07:00
|
|
|
See the [bank.Keeper API docs](TODO) 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
|
|
|
|
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`,
|
|
|
|
we restrict access to a small number of functions that do very specific things.
|
2018-06-27 07:08:55 -07:00
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
## App3
|
2018-06-26 14:21:46 -07:00
|
|
|
|
2018-06-27 09:45:01 -07:00
|
|
|
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:
|
2018-06-26 14:21:46 -07:00
|
|
|
|
|
|
|
```go
|
2018-06-26 19:49:13 -07:00
|
|
|
TODO
|
2018-06-26 14:21:46 -07:00
|
|
|
```
|