From f405bdf76152469d30db440f71a58a20e7e7b013 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 29 Jun 2018 00:41:44 -0400 Subject: [PATCH] template app4.md. simplify app4.go --- docs/README.md | 18 +++-- docs/core/app1.md | 20 +++-- docs/core/app4.md | 73 +++++++++++++++++ docs/core/examples/app4.go | 162 +++---------------------------------- 4 files changed, 107 insertions(+), 166 deletions(-) diff --git a/docs/README.md b/docs/README.md index 341000a941..219166e904 100644 --- a/docs/README.md +++ b/docs/README.md @@ -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 diff --git a/docs/core/app1.md b/docs/core/app1.md index ef56b50813..ac07e4755d 100644 --- a/docs/core/app1.md +++ b/docs/core/app1.md @@ -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`): diff --git a/docs/core/app4.md b/docs/core/app4.md index e69de29bb2..d5d70bc3f4 100644 --- a/docs/core/app4.md +++ b/docs/core/app4.md @@ -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. diff --git a/docs/core/examples/app4.go b/docs/core/examples/app4.go index 5494e1bd2d..0b27688549 100644 --- a/docs/core/examples/app4.go +++ b/docs/core/examples/app4.go @@ -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" @@ -31,28 +26,27 @@ 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()) @@ -60,10 +54,9 @@ func NewApp4(logger log.Logger, db dbm.DB) *bapp.BaseApp { 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 @@ -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