Skip to content

Commit

Permalink
template app4.md. simplify app4.go
Browse files Browse the repository at this point in the history
  • Loading branch information
ebuchman committed Jun 29, 2018
1 parent 1d4d9e9 commit f405bdf
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 166 deletions.
18 changes: 11 additions & 7 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ NOTE: This documentation is a work-in-progress!
verifies `StdTx`, manages accounts, and deducts fees
- [bank.CoinKeeper](core/app3.md#coin-keeper) - CoinKeeper allows for coin
transfers on an underlying AccountMapper
- [App4 - Validator Set Changes](core/app4.md)
- [InitChain](core/app4.md#init-chain) - Initialize the application
state
- [BeginBlock](core/app4.md#begin-block) - BeginBlock logic runs at the
beginning of every block
- [EndBlock](core/app4.md#end-block) - EndBlock logic runs at the
end of every block
- [App4 - ABCI](core/app4.md)
- [ABCI](core/app4.md#abci) - ABCI is the interface between Tendermint
and the Cosmos-SDK
- [InitChain](core/app4.md#initchain) - Initialize the application
store
- [BeginBlock](core/app4.md#beginblock) - BeginBlock runs at the
beginning of every block and updates the app about validator behaviour
- [EndBlock](core/app4.md#endblock) - EndBlock runs at the
end of every block and lets the app change the validator set.
- [Query](core/app4.md#query) - Query the application store
- [CheckTx](core/app4.md#checktx) - CheckTx only runs the AnteHandler
- [App5 - Basecoin](core/app5.md) -
- [Directory Structure](core/app5.md#directory-structure) - Keep your
application code organized
Expand Down
20 changes: 12 additions & 8 deletions docs/core/app1.md
Original file line number Diff line number Diff line change
Expand Up @@ -464,14 +464,18 @@ Since we only have one store, we only mount one.

## Execution

We're now done the core logic of the app! From here, we could write transactions
in Go and execute them against the application using the `app.DeliverTx` method.
In a real setup, the app would run as an ABCI application and
would be driven by blocks of transactions from the Tendermint consensus engine.
Later in the tutorial, we'll connect our app to a complete suite of components
for running and using a live blockchain application. For complete details on
how ABCI applications work, see the [ABCI
documentation](https://github.com/tendermint/abci/blob/master/specification.md).
We're now done the core logic of the app! From here, we can write tests in Go
that initialize the store with accounts and execute transactions by calling
the `app.DeliverTx` method.

In a real setup, the app would run as an ABCI application on top of the
Tendermint consensus engine. It would be initialized by a Genesis file, and it
would be driven by blocks of transactions committed by the underlying Tendermint
consensus. We'll talk more about ABCI and how this all works a bit later, but
feel free to check the
[specification](https://github.com/tendermint/abci/blob/master/specification.md).
We'll also see how to connect our app to a complete suite of components
for running and using a live blockchain application.

For now, we note the follow sequence of events occurs when a transaction is
received (through `app.DeliverTx`):
Expand Down
73 changes: 73 additions & 0 deletions docs/core/app4.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# ABCI

The Application BlockChain Interface, or ABCI, is a powerfully
delineated boundary between the Cosmos-SDK and Tendermint.
It separates the logical state transition machine of your application from
its secure replication across many physical machines.

By providing a clear, language agnostic boundary between applications and consensus,
ABCI provides tremendous developer flexibility and [support in many
languages](https://tendermint.com/ecosystem). That said, it is still quite a low-level protocol, and
requires frameworks to be built to abstract over that low-level componentry.
The Cosmos-SDK is one such framework.

While we've already seen `DeliverTx`, the workhorse of any ABCI application,
here we will introduce the other ABCI requests sent by Tendermint, and
how we can use them to build more advanced applications. For a more complete
depiction of the ABCI and how its used, see
[the
specification](https://github.com/tendermint/abci/blob/master/specification.md)

## InitChain

In our previous apps, we built out all the core logic, but we never specified
how the store should be initialized. For that, we use the `app.InitChain` method,
which is called once by Tendermint the very first time the application boots up.

The InitChain request contains a variety of Tendermint information, like the consensus
parameters and an initial validator set, but it also contains an opaque blob of
application specific bytes - typically JSON encoded.
Apps can decide what to do with all of this information by calling the
`app.SetInitChainer` method.

For instance, let's introduce a `GenesisAccount` struct that can be JSON encoded
and part of a genesis file. Then we can populate the store with such accounts
during InitChain:

```go
TODO
```

If we include a correctly formatted `GenesisAccount` in our Tendermint
genesis.json file, the store will be initialized with those accounts and they'll
be able to send transactions!

## BeginBlock

BeginBlock is called at the beginning of each block, before processing any
transactions with DeliverTx.
It contains information on what validators have signed.

## EndBlock

EndBlock is called at the end of each block, after processing all transactions
with DeliverTx.
It allows the application to return updates to the validator set.

## Commit

Commit is called after EndBlock. It persists the application state and returns
the Merkle root hash to be included in the next Tendermint block. The root hash
can be in Query for Merkle proofs of the state.

## Query

Query allows queries into the application store according to a path.

## CheckTx

CheckTx is used for the mempool. It only runs the AnteHandler. This is so
potentially expensive message handling doesn't begin until the transaction has
actually been committed in a block. The AnteHandler authenticates the sender and
ensures they have enough to pay the fee for the transaction. If the transaction
later fails, the sender still pays the fee.
162 changes: 11 additions & 151 deletions docs/core/examples/app4.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
package app

import (
"bytes"
"encoding/json"
"fmt"
"reflect"

abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
dbm "github.com/tendermint/tmlibs/db"
Expand All @@ -31,39 +26,37 @@ func NewApp4(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")

// Set various mappers/keepers to interact easily with underlying stores
accountMapper := auth.NewAccountMapper(cdc, keyAccount, &auth.BaseAccount{})
accountKeeper := bank.NewKeeper(accountMapper)
metadataMapper := NewApp4MetaDataMapper(keyMain)
coinKeeper := bank.NewKeeper(accountMapper)

// TODO
keyFees := sdk.NewKVStoreKey("fee")
feeKeeper := auth.NewFeeCollectionKeeper(cdc, keyFees)

app.SetAnteHandler(auth.NewAnteHandler(accountMapper, feeKeeper))

// Set InitChainer
app.SetInitChainer(NewInitChainer(cdc, accountMapper, metadataMapper))
app.SetInitChainer(NewInitChainer(cdc, accountMapper))

// Register message routes.
// Note the handler gets access to the account store.
app.Router().
AddRoute("send", betterHandleMsgSend(accountKeeper)).
AddRoute("issue", evenBetterHandleMsgIssue(metadataMapper, accountKeeper))
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
}

// Application state at Genesis has accounts with starting balances and coins with starting metadata
// Application state at Genesis has accounts with starting balances
type GenesisState struct {
Accounts []*GenesisAccount `json:"accounts"`
Coins []*GenesisCoin `json:"coins"`
}

// GenesisAccount doesn't need pubkey or sequence
Expand All @@ -81,160 +74,27 @@ func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount, err error) {
return &baseAcc, nil
}

// GenesisCoin enforces CurrentSupply is 0 at genesis.
type GenesisCoin struct {
Denom string `json:"denom"`
Issuer sdk.Address `json:"issuer"`
TotalSupply sdk.Int `json:"total_supply`
Decimal uint64 `json:"decimals"`
}

// Converts GenesisCoin to its denom and metadata for storage in main store
func (gc *GenesisCoin) ToMetaData() (string, CoinMetadata) {
return gc.Denom, CoinMetadata{
Issuer: gc.Issuer,
TotalSupply: gc.TotalSupply,
Decimal: gc.Decimal,
}
}

// InitChainer will set initial balances for accounts as well as initial coin metadata
// MsgIssue can no longer be used to create new coin
func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper, metadataMapper MetaDataMapper) sdk.InitChainer {
func NewInitChainer(cdc *wire.Codec, accountMapper auth.AccountMapper) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
stateJSON := req.AppStateBytes

genesisState := new(GenesisState)
err := cdc.UnmarshalJSON(stateJSON, genesisState)
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
panic(err)
}

for _, gacc := range genesisState.Accounts {
acc, err := gacc.ToAccount()
if err != nil {
panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468
// return sdk.ErrGenesisParse("").TraceCause(err, "")
panic(err)
}
acc.AccountNumber = accountMapper.GetNextAccountNumber(ctx)
accountMapper.SetAccount(ctx, acc)
}

// Initialize coin metadata.
for _, gc := range genesisState.Coins {
denom, metadata := gc.ToMetaData()
metadataMapper.SetMetaData(ctx, denom, metadata)
}

return abci.ResponseInitChain{}

}
}

//---------------------------------------------------------------------------------------------
// Now that initializing coin metadata is done in InitChainer we can simplify handleMsgIssue

// New MsgIssue handler will no longer generate coin metadata on the fly.
// Allows issuers (permissioned at genesis) to issue coin to receiver.
func evenBetterHandleMsgIssue(metadataMapper MetaDataMapper, accountKeeper 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 := evenBetterHandleMetaData(ctx, metadataMapper, issueMsg.Issuer, issueMsg.Coin); !res.IsOK() {
return res
}

// Add newly issued coins to output address
_, _, err := accountKeeper.AddCoins(ctx, issueMsg.Receiver, []sdk.Coin{issueMsg.Coin})
if err != nil {
return err.Result()
}

return sdk.Result{
// Return result with Issue msg tags
Tags: issueMsg.Tags(),
}
}
}

// No longer generates metadata on the fly.
// Returns error result when it cannot find coin metadata
func evenBetterHandleMetaData(ctx sdk.Context, metadataMapper MetaDataMapper, issuer sdk.Address, coin sdk.Coin) sdk.Result {
metadata := metadataMapper.GetMetaData(ctx, coin.Denom)

// Coin metadata does not exist in store
if reflect.DeepEqual(metadata, CoinMetadata{}) {
return sdk.ErrInvalidCoins(fmt.Sprintf("Cannot find metadata for coin: %s", coin.Denom)).Result()
}

// Msg Issuer not authorized to issue these coins
if !bytes.Equal(metadata.Issuer, issuer) {
return sdk.ErrUnauthorized(fmt.Sprintf("Msg Issuer cannot issue tokens: %s", coin.Denom)).Result()
}

// Update current circulating supply
metadata.CurrentSupply = metadata.CurrentSupply.Add(coin.Amount)

// Current supply cannot exceed total supply
if metadata.TotalSupply.LT(metadata.CurrentSupply) {
return sdk.ErrInsufficientCoins("Issuer cannot issue more than total supply of coin").Result()
}

metadataMapper.SetMetaData(ctx, coin.Denom, metadata)
return sdk.Result{}
}

//---------------------------------------------------------------------------------------------
// Simpler MetaDataMapper no longer able to initialize default CoinMetaData

// Implements MetaDataMapper
type App4MetaDataMapper struct {
mainKey *sdk.KVStoreKey
}

// Constructs new App4MetaDataMapper
func NewApp4MetaDataMapper(key *sdk.KVStoreKey) App4MetaDataMapper {
return App4MetaDataMapper{mainKey: key}
}

// Returns coin Metadata. If metadata not found in store, function returns empty struct.
func (mdm App4MetaDataMapper) GetMetaData(ctx sdk.Context, denom string) CoinMetadata {
store := ctx.KVStore(mdm.mainKey)

bz := store.Get([]byte(denom))
if bz == nil {
// Coin metadata doesn't exist, create new metadata with default params
return CoinMetadata{}
}

var metadata CoinMetadata
err := json.Unmarshal(bz, &metadata)
if err != nil {
panic(err)
}

return metadata
}

// Sets metadata in store with key equal to coin denom. Same behavior as App3 implementation.
func (mdm App4MetaDataMapper) SetMetaData(ctx sdk.Context, denom string, metadata CoinMetadata) {
store := ctx.KVStore(mdm.mainKey)

val, err := json.Marshal(metadata)
if err != nil {
panic(err)
}

store.Set([]byte(denom), val)
}

//------------------------------------------------------------------
// AccountMapper

//------------------------------------------------------------------
// CoinsKeeper

0 comments on commit f405bdf

Please sign in to comment.