2018-06-26 19:49:13 -07:00
|
|
|
# Transactions
|
2018-06-16 22:56:53 -07:00
|
|
|
|
2018-07-23 21:13:55 -07:00
|
|
|
In the previous app we built a simple bank with one message type `send` for sending
|
2018-06-26 15:05:12 -07:00
|
|
|
coins and one store for storing accounts.
|
2018-06-26 19:49:13 -07:00
|
|
|
Here we build `App2`, which expands on `App1` by introducing
|
2018-06-26 15:05:12 -07:00
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
- a new message type for issuing new coins
|
|
|
|
- a new store for coin metadata (like who can issue coins)
|
|
|
|
- a requirement that transactions include valid signatures
|
2018-06-26 15:05:12 -07:00
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
Along the way, we'll be introduced to Amino for encoding and decoding
|
|
|
|
transactions and to the AnteHandler for processing them.
|
2018-06-26 15:05:12 -07:00
|
|
|
|
2018-06-28 16:41:40 -07:00
|
|
|
The complete code can be found in [app2.go](examples/app2.go).
|
|
|
|
|
2018-06-26 15:05:12 -07:00
|
|
|
|
|
|
|
## Message
|
|
|
|
|
|
|
|
Let's introduce a new message type for issuing coins:
|
|
|
|
|
|
|
|
```go
|
2018-06-28 16:41:40 -07:00
|
|
|
// MsgIssue to allow a registered issuer
|
|
|
|
// to issue new coins.
|
|
|
|
type MsgIssue struct {
|
2018-07-09 02:15:36 -07:00
|
|
|
Issuer sdk.AccAddress
|
|
|
|
Receiver sdk.AccAddress
|
2018-06-28 16:41:40 -07:00
|
|
|
Coin sdk.Coin
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implements Msg.
|
2018-10-23 12:23:55 -07:00
|
|
|
func (msg MsgIssue) Route() string { return "issue" }
|
2018-06-26 15:05:12 -07:00
|
|
|
```
|
|
|
|
|
2018-10-23 12:23:55 -07:00
|
|
|
Note the `Route()` method returns `"issue"`, so this message is of a different
|
|
|
|
route and will be executed by a different handler than `MsgSend`. The other
|
2018-06-28 16:41:40 -07:00
|
|
|
methods for `MsgIssue` are similar to `MsgSend`.
|
|
|
|
|
2018-06-26 15:05:12 -07:00
|
|
|
## Handler
|
|
|
|
|
2018-06-28 16:41:40 -07:00
|
|
|
We'll need a new handler to support the new message type. It just checks if the
|
|
|
|
sender of the `MsgIssue` is the correct issuer for the given coin type, as per the information
|
|
|
|
in the issuer store:
|
2018-06-26 15:05:12 -07:00
|
|
|
|
|
|
|
```go
|
2018-06-28 16:41:40 -07:00
|
|
|
// Handle MsgIssue
|
|
|
|
func handleMsgIssue(keyIssue *sdk.KVStoreKey, keyAcc *sdk.KVStoreKey) sdk.Handler {
|
|
|
|
return func(ctx sdk.Context, msg sdk.Msg) sdk.Result {
|
|
|
|
issueMsg, ok := msg.(MsgIssue)
|
|
|
|
if !ok {
|
|
|
|
return sdk.NewError(2, 1, "MsgIssue is malformed").Result()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Retrieve stores
|
|
|
|
issueStore := ctx.KVStore(keyIssue)
|
|
|
|
accStore := ctx.KVStore(keyAcc)
|
|
|
|
|
|
|
|
// Handle updating coin info
|
|
|
|
if res := handleIssuer(issueStore, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
// Issue coins to receiver using previously defined handleTo function
|
|
|
|
if res := handleTo(accStore, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin}); !res.IsOK() {
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
return sdk.Result{
|
|
|
|
// Return result with Issue msg tags
|
|
|
|
Tags: issueMsg.Tags(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 02:15:36 -07:00
|
|
|
func handleIssuer(store sdk.KVStore, issuer sdk.AccAddress, coin sdk.Coin) sdk.Result {
|
2018-06-28 16:41:40 -07:00
|
|
|
// the issuer address is stored directly under the coin denomination
|
|
|
|
denom := []byte(coin.Denom)
|
2018-06-28 17:08:38 -07:00
|
|
|
infoBytes := store.Get(denom)
|
|
|
|
if infoBytes == nil {
|
2018-06-28 16:41:40 -07:00
|
|
|
return sdk.ErrInvalidCoins(fmt.Sprintf("Unknown coin type %s", coin.Denom)).Result()
|
|
|
|
}
|
|
|
|
|
2018-06-28 17:08:38 -07:00
|
|
|
var coinInfo coinInfo
|
|
|
|
err := json.Unmarshal(infoBytes, &coinInfo)
|
|
|
|
if err != nil {
|
|
|
|
return sdk.ErrInternal("Error when deserializing coinInfo").Result()
|
|
|
|
}
|
|
|
|
|
2018-06-28 16:41:40 -07:00
|
|
|
// Msg Issuer is not authorized to issue these coins
|
2018-06-28 17:08:38 -07:00
|
|
|
if !bytes.Equal(coinInfo.Issuer, issuer) {
|
2018-06-28 16:41:40 -07:00
|
|
|
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
|
|
|
|
}
|
|
|
|
|
|
|
|
return sdk.Result{}
|
|
|
|
}
|
2018-06-28 17:08:38 -07:00
|
|
|
|
|
|
|
// coinInfo stores meta data about a coin
|
|
|
|
type coinInfo struct {
|
2018-07-09 02:15:36 -07:00
|
|
|
Issuer sdk.AccAddress `json:"issuer"`
|
2018-06-28 17:08:38 -07:00
|
|
|
}
|
2018-06-26 15:05:12 -07:00
|
|
|
```
|
|
|
|
|
2018-06-28 17:08:38 -07:00
|
|
|
Note we've introduced the `coinInfo` type to store the issuer address for each coin.
|
|
|
|
We JSON serialize this type and store it directly under the denomination in the
|
|
|
|
issuer store. We could of course add more fields and logic around this,
|
|
|
|
like including the current supply of coins in existence, and enforcing a maximum supply,
|
|
|
|
but that's left as an excercise for the reader :).
|
2018-06-28 16:41:40 -07:00
|
|
|
|
2018-06-26 15:05:12 -07:00
|
|
|
## Amino
|
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
Now that we have two implementations of `Msg`, we won't know before hand
|
|
|
|
which type is contained in a serialized `Tx`. Ideally, we would use the
|
|
|
|
`Msg` interface inside our `Tx` implementation, but the JSON decoder can't
|
|
|
|
decode into interface types. In fact, there's no standard way to unmarshal
|
|
|
|
into interfaces in Go. This is one of the primary reasons we built
|
|
|
|
[Amino](https://github.com/tendermint/go-amino) :).
|
2018-06-16 22:56:53 -07:00
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
While SDK developers can encode transactions and state objects however they
|
|
|
|
like, Amino is the recommended format. The goal of Amino is to improve over the latest version of Protocol Buffers,
|
2018-06-16 22:56:53 -07:00
|
|
|
`proto3`. To that end, Amino is compatible with the subset of `proto3` that
|
|
|
|
excludes the `oneof` keyword.
|
|
|
|
|
|
|
|
While `oneof` provides union types, Amino aims to provide interfaces.
|
|
|
|
The main difference being that with union types, you have to know all the types
|
|
|
|
up front. But anyone can implement an interface type whenever and however
|
|
|
|
they like.
|
|
|
|
|
|
|
|
To implement interface types, Amino allows any concrete implementation of an
|
|
|
|
interface to register a globally unique name that is carried along whenever the
|
|
|
|
type is serialized. This allows Amino to seamlessly deserialize into interface
|
|
|
|
types!
|
|
|
|
|
|
|
|
The primary use for Amino in the SDK is for messages that implement the
|
|
|
|
`Msg` interface. By registering each message with a distinct name, they are each
|
|
|
|
given a distinct Amino prefix, allowing them to be easily distinguished in
|
|
|
|
transactions.
|
|
|
|
|
|
|
|
Amino can also be used for persistent storage of interfaces.
|
|
|
|
|
|
|
|
To use Amino, simply create a codec, and then register types:
|
|
|
|
|
|
|
|
```
|
2018-09-13 11:17:32 -07:00
|
|
|
func NewCodec() *codec.Codec {
|
|
|
|
cdc := codec.New()
|
2018-06-27 05:24:40 -07:00
|
|
|
cdc.RegisterInterface((*sdk.Msg)(nil), nil)
|
|
|
|
cdc.RegisterConcrete(MsgSend{}, "example/MsgSend", nil)
|
|
|
|
cdc.RegisterConcrete(MsgIssue{}, "example/MsgIssue", nil)
|
2018-07-23 21:13:55 -07:00
|
|
|
crypto.RegisterAmino(cdc)
|
2018-06-27 05:24:40 -07:00
|
|
|
return cdc
|
|
|
|
}
|
2018-06-16 22:56:53 -07:00
|
|
|
```
|
2018-06-26 19:49:13 -07:00
|
|
|
|
2018-07-23 21:13:55 -07:00
|
|
|
Note: We also register the types in the `tendermint/tendermint/crypto` module so that `crypto.PubKey`
|
2018-08-12 00:33:48 -07:00
|
|
|
is encoded/decoded correctly.
|
2018-07-23 21:13:55 -07:00
|
|
|
|
2018-06-27 05:24:40 -07:00
|
|
|
Amino supports encoding and decoding in both a binary and JSON format.
|
|
|
|
See the [codec API docs](https://godoc.org/github.com/tendermint/go-amino#Codec) for more details.
|
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
## Tx
|
|
|
|
|
2018-06-27 05:24:40 -07:00
|
|
|
Now that we're using Amino, we can embed the `Msg` interface directly in our
|
|
|
|
`Tx`. We can also add a public key and a signature for authentication.
|
|
|
|
|
|
|
|
```go
|
|
|
|
// Simple tx to wrap the Msg.
|
|
|
|
type app2Tx struct {
|
2018-06-28 16:06:10 -07:00
|
|
|
sdk.Msg
|
|
|
|
|
2018-06-27 05:24:40 -07:00
|
|
|
PubKey crypto.PubKey
|
2018-08-12 00:33:48 -07:00
|
|
|
Signature []byte
|
2018-06-27 05:24:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// This tx only has one Msg.
|
|
|
|
func (tx app2Tx) GetMsgs() []sdk.Msg {
|
|
|
|
return []sdk.Msg{tx.Msg}
|
|
|
|
}
|
|
|
|
|
2018-07-18 16:32:50 -07:00
|
|
|
// Amino decode app2Tx. Capable of decoding both MsgSend and MsgIssue
|
2018-09-13 11:17:32 -07:00
|
|
|
func tx2Decoder(cdc *codec.Codec) sdk.TxDecoder {
|
2018-07-18 16:32:50 -07:00
|
|
|
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
|
|
var tx app2Tx
|
2018-11-04 18:28:38 -08:00
|
|
|
err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &tx)
|
2018-07-18 16:32:50 -07:00
|
|
|
if err != nil {
|
|
|
|
return nil, sdk.ErrTxDecode(err.Error())
|
|
|
|
}
|
|
|
|
return tx, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
```
|
2018-06-26 19:49:13 -07:00
|
|
|
|
|
|
|
## AnteHandler
|
|
|
|
|
2018-06-27 05:24:40 -07:00
|
|
|
Now that we have an implementation of `Tx` that includes more than just the Msg,
|
2018-06-26 19:49:13 -07:00
|
|
|
we need to specify how that extra information is validated and processed. This
|
|
|
|
is the role of the `AnteHandler`. The word `ante` here denotes "before", as the
|
2018-06-27 05:24:40 -07:00
|
|
|
`AnteHandler` is run before a `Handler`. While an app can have many Handlers,
|
|
|
|
one for each set of messages, it can have only a single `AnteHandler` that
|
2018-06-26 19:49:13 -07:00
|
|
|
corresponds to its single implementation of `Tx`.
|
|
|
|
|
|
|
|
|
|
|
|
The AnteHandler resembles a Handler:
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool)
|
|
|
|
```
|
|
|
|
|
|
|
|
Like Handler, AnteHandler takes a Context that restricts its access to stores
|
|
|
|
according to whatever capability keys it was granted. Instead of a `Msg`,
|
|
|
|
however, it takes a `Tx`.
|
|
|
|
|
|
|
|
Like Handler, AnteHandler returns a `Result` type, but it also returns a new
|
2018-06-28 16:41:40 -07:00
|
|
|
`Context` and an `abort bool`.
|
2018-06-26 19:49:13 -07:00
|
|
|
|
|
|
|
For `App2`, we simply check if the PubKey matches the Address, and the Signature validates with the PubKey:
|
|
|
|
|
|
|
|
```go
|
2018-06-28 16:41:40 -07:00
|
|
|
// Simple anteHandler that ensures msg signers have signed.
|
|
|
|
// Provides no replay protection.
|
|
|
|
func antehandler(ctx sdk.Context, tx sdk.Tx) (_ sdk.Context, _ sdk.Result, abort bool) {
|
|
|
|
appTx, ok := tx.(app2Tx)
|
|
|
|
if !ok {
|
|
|
|
// set abort boolean to true so that we don't continue to process failed tx
|
|
|
|
return ctx, sdk.ErrTxDecode("Tx must be of format app2Tx").Result(), true
|
|
|
|
}
|
|
|
|
|
2018-07-23 21:13:55 -07:00
|
|
|
// expect only one msg and one signer in app2Tx
|
2018-06-28 16:41:40 -07:00
|
|
|
msg := tx.GetMsgs()[0]
|
2018-07-23 21:13:55 -07:00
|
|
|
signerAddr := msg.GetSigners()[0]
|
2018-06-28 16:41:40 -07:00
|
|
|
|
|
|
|
signBytes := msg.GetSignBytes()
|
|
|
|
|
2018-07-23 21:13:55 -07:00
|
|
|
sig := appTx.GetSignature()
|
2018-06-28 16:41:40 -07:00
|
|
|
|
2018-07-23 21:13:55 -07:00
|
|
|
// check that submitted pubkey belongs to required address
|
|
|
|
if !bytes.Equal(appTx.PubKey.Address(), signerAddr) {
|
|
|
|
return ctx, sdk.ErrUnauthorized("Provided Pubkey does not match required address").Result(), true
|
|
|
|
}
|
|
|
|
|
|
|
|
// check that signature is over expected signBytes
|
|
|
|
if !appTx.PubKey.VerifyBytes(signBytes, sig) {
|
|
|
|
return ctx, sdk.ErrUnauthorized("Signature verification failed").Result(), true
|
2018-06-28 16:41:40 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// authentication passed, app to continue processing by sending msg to handler
|
|
|
|
return ctx, sdk.Result{}, false
|
|
|
|
}
|
2018-06-26 19:49:13 -07:00
|
|
|
```
|
|
|
|
|
|
|
|
## App2
|
|
|
|
|
|
|
|
Let's put it all together now to get App2:
|
|
|
|
|
|
|
|
```go
|
2018-06-28 16:41:40 -07:00
|
|
|
func NewApp2(logger log.Logger, db dbm.DB) *bapp.BaseApp {
|
|
|
|
|
|
|
|
cdc := NewCodec()
|
|
|
|
|
|
|
|
// Create the base application object.
|
2018-07-18 16:32:50 -07:00
|
|
|
app := bapp.NewBaseApp(app2Name, logger, db, txDecoder(cdc))
|
2018-06-28 16:41:40 -07:00
|
|
|
|
|
|
|
// Create a key for accessing the account store.
|
|
|
|
keyAccount := sdk.NewKVStoreKey("acc")
|
|
|
|
// Create a key for accessing the issue store.
|
|
|
|
keyIssue := sdk.NewKVStoreKey("issue")
|
|
|
|
|
|
|
|
// set antehandler function
|
|
|
|
app.SetAnteHandler(antehandler)
|
|
|
|
|
|
|
|
// Register message routes.
|
|
|
|
// Note the handler gets access to the account store.
|
|
|
|
app.Router().
|
|
|
|
AddRoute("send", handleMsgSend(keyAccount)).
|
|
|
|
AddRoute("issue", handleMsgIssue(keyAccount, keyIssue))
|
|
|
|
|
|
|
|
// Mount stores and load the latest state.
|
|
|
|
app.MountStoresIAVL(keyAccount, keyIssue)
|
|
|
|
err := app.LoadLatestVersion(keyAccount)
|
|
|
|
if err != nil {
|
|
|
|
cmn.Exit(err.Error())
|
|
|
|
}
|
|
|
|
return app
|
|
|
|
}
|
2018-06-26 19:49:13 -07:00
|
|
|
```
|
|
|
|
|
2018-06-28 16:41:40 -07:00
|
|
|
The main difference here, compared to `App1`, is that we use a second capability
|
|
|
|
key for a second store that is *only* passed to a second handler, the
|
|
|
|
`handleMsgIssue`. The first `handleMsgSend` has no access to this second store and cannot read or write to
|
|
|
|
it, ensuring a strong separation of concerns.
|
|
|
|
|
2018-07-18 16:32:50 -07:00
|
|
|
Note now that we're using Amino, we create a codec, register our types on the codec, and pass the
|
|
|
|
codec into our TxDecoder constructor, `tx2Decoder`. The SDK takes care of the rest for us!
|
2018-06-28 16:41:40 -07:00
|
|
|
|
2018-06-26 19:49:13 -07:00
|
|
|
## Conclusion
|
|
|
|
|
|
|
|
We've expanded on our first app by adding a new message type for issuing coins,
|
|
|
|
and by checking signatures. We learned how to use Amino for decoding into
|
|
|
|
interface types, allowing us to support multiple Msg types, and we learned how
|
|
|
|
to use the AnteHandler to validate transactions.
|
|
|
|
|
|
|
|
Unfortunately, our application is still insecure, because any valid transaction
|
|
|
|
can be replayed multiple times to drain someones account! Besides, validating
|
|
|
|
signatures and preventing replays aren't things developers should have to think
|
|
|
|
about.
|
|
|
|
|
|
|
|
In the next section, we introduce the built-in SDK modules `auth` and `bank`,
|
|
|
|
which respectively provide secure implementations for all our transaction authentication
|
|
|
|
and coin transfering needs.
|