cosmos-sdk/docs/core/app3.md

5.8 KiB

Modules

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.

Here, in App3, we introduce two built-in SDK modules to replace the Msg, Tx, Handler, and AnteHandler implementations we've seen so far.

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.

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.

Accounts

The x/auth module defines a model of accounts much like Ethereum. In this model, an account contains:

  • Address for identification
  • PubKey for authentication
  • AccountNumber to prune empty accounts
  • Sequence to prevent transaction replays
  • Coins to carry a balance

Account

The Account interface captures this account model with getters and setters:

// 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
}

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

The default implementation of Account is the BaseAccount:

// 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.

The Address, PubKey, and AccountNumber of the BaseAccpunt cannot be changed once they are set.

The Sequence increments by one with every transaction. This ensures that a given transaction can only be executed once, as the Sequence contained in the transaction must match that contained in the account.

The Coins will change according to the logic of each transaction type.

If the Coins are ever emptied, the account will be deleted from the store. If coins are later sent to the same Address, the account will be recreated but with a new AccountNumber. This allows us to prune empty accounts from the store, while still preventing transaction replay if accounts become non-empty again in the future.

AccountMapper

TODO

Transaction

StdTx

The standard way to create a transaction from a message is to use the StdTx struct defined in the x/auth module:

// 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).
type StdTx struct {
	Msgs       []sdk.Msg      `json:"msg"`
	Fee        StdFee         `json:"fee"`
	Signatures []StdSignature `json:"signatures"`
	Memo       string         `json:"memo"`
}

The StdTx includes a list of messages, information about the fee being paid, and a list of signatures. It also includes an optional Memo for additional data. Note that the list of signatures must match the result of GetSigners() for each Msg!

The signatures are provided in a standard form as StdSignature:

// 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.
type StdSignature struct {
	crypto.PubKey    `json:"pub_key"` // optional
	crypto.Signature `json:"signature"`
	AccountNumber    int64 `json:"account_number"`
	Sequence         int64 `json:"sequence"`
}

Recall that the Sequence is expected to increment every time a message is signed by a given account in order to prevent "replay attacks" where the same message could be executed over and over again. The AccountNumber is assigned when the account is created or recreated after being emptied.

The StdSignature can also optionally include the public key for verifying the signature. The public key only needs to be included the first time a transaction is sent from a given account - from then on it will be stored in the Account and can be left out of transactions.

The fee is provided in a standard form as StdFee:

// 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"`
}

Note that the address responsible for paying the transactions fee is the first address returned by msg.GetSigners() for the first Msg. The convenience function FeePayer(tx Tx) is provided to return this.

Signing

The standard bytes for signers to sign over is provided by:

TODO

AnteHandler

TODO

App3

Putting it all together, we get:

TODO