From eb5113af558406122ec8f6a20b0039809bfce77b Mon Sep 17 00:00:00 2001 From: Aditya Sripal Date: Thu, 14 Jun 2018 19:18:48 -0700 Subject: [PATCH 1/2] Added documentation to guide --- docs/guides/guide.md | 187 ++++++++++++++++++++++++++++++++++++++----- types/store.go | 3 +- 2 files changed, 167 insertions(+), 23 deletions(-) diff --git a/docs/guides/guide.md b/docs/guides/guide.md index db5ba392e3..8c7a9bca62 100644 --- a/docs/guides/guide.md +++ b/docs/guides/guide.md @@ -99,18 +99,20 @@ type Tx interface { GetMsg() Msg - // Signatures returns the signature of signers who signed the Msg. - // CONTRACT: Length returned is same as length of - // pubkeys returned from MsgKeySigners, and the order - // matches. - // CONTRACT: If the signature is missing (ie the Msg is - // invalid), then the corresponding signature is - // .Empty(). - GetSignatures() []StdSignature } ``` -The `tx.GetSignatures()` method returns a list of signatures, which must match +The standard way to create a transaction from a message is to use the `StdTx` struct defined in the `x/auth` module. + +```go +type StdTx struct { + Msg sdk.Msg `json:"msg"` + Fee StdFee `json:"fee"` + Signatures []StdSignature `json:"signatures"` +} +``` + +The `StdTx.GetSignatures()` method returns a list of signatures, which must match the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in a standard form: @@ -118,6 +120,7 @@ a standard form: type StdSignature struct { crypto.PubKey // optional crypto.Signature + AccountNumber int64 Sequence int64 } ``` @@ -138,12 +141,21 @@ The address responsible for paying the transactions fee is the first address returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided to return this. -The standard way to create a transaction from a message is to use the `StdTx`: +The standard bytes for signers to sign over is provided by: ```go -type StdTx struct { - Msg - Signatures []StdSignature +func StdSignByes(chainID string, accnums []int64, sequences []int64, fee StdFee, msg sdk.Msg) []byte +``` + +in `x/auth`. The standard way to construct fees to pay for the processing of transactions is: + +```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"` } ``` @@ -154,7 +166,7 @@ specify their own encoding schemes. This enables the SDK to be used as the framwork for constructing already specified cryptocurrency state machines, for instance Ethereum. -When initializing an application, a developer must specify a `TxDecoder` +When initializing an application, a developer can specify a `TxDecoder` function which determines how an arbitrary byte array should be unmarshalled into a `Tx`: @@ -162,8 +174,10 @@ into a `Tx`: type TxDecoder func(txBytes []byte) (Tx, error) ``` -In `Basecoin`, we use the Tendermint wire format and the `go-amino` library for -encoding and decoding all message types. The `go-amino` library has the nice +The default tx decoder is the Tendermint wire format which uses the go-amino library +for encoding and decoding all message types. + +In `Basecoin`, we use the default transaction decoder. The `go-amino` library has the nice property that it can unmarshal into interface types, but it requires the relevant types to be registered ahead of type. Registration happens on a `Codec` object, so as not to taint the global name space. @@ -182,6 +196,14 @@ unique "prefix bytes" during encoding. A registered type will always use the same prefix-bytes, regardless of what interface it is satisfying. For more details, see the [go-amino documentation](https://github.com/tendermint/go-amino/blob/develop). +If you wish to use a custom encoding scheme, you must define a TxDecoder function +and set it as the decoder in your extended baseapp using the `SetTxDecoder(decoder sdk.TxDecoder)`. + +Ex: + +```go +app.SetTxDecoder(CustomTxDecodeFn) +``` ## Storage @@ -252,14 +274,14 @@ Many methods on SDK objects receive a context as the first argument. ## Handler -Transaction processing in the SDK is defined through `Handler` functions: +Message processing in the SDK is defined through `Handler` functions: ```go -type Handler func(ctx Context, tx Tx) Result +type Handler func(ctx Context, msg Msg) Result ``` -A handler takes a context and a transaction and returns a result. All -information necessary for processing a transaction should be available in the +A handler takes a context and a message and returns a result. All +information necessary for processing a message should be available in the context. While the context holds the entire application state (all referenced from the @@ -291,15 +313,138 @@ func NewHandler(am sdk.AccountMapper) sdk.Handler { ## AnteHandler +The AnteHandler is used to do all transaction-level processing (i.e. Fee payment, signature verification) +before passing the message to its respective handler. + +```go +type AnteHandler func(ctx Context, tx Tx) (newCtx Context, result Result, abort bool) +``` + +The antehandler takes a Context and a transaction and returns a new Context, a Result, and the abort boolean. +As with the handler, all information necessary for processing a message should be available in the +context. + +If the transaction fails, then the application should not waste time processing the message. Thus, the antehandler should +return an Error's Result method and set the abort boolean to `true` so that the application knows not to process the message in a handler. + +Most applications can use the provided antehandler implementation in `x/auth` which handles signature verification +as well as collecting fees. + +Note: Signatures must be over `auth.StdSignDoc` introduced above to use the provided antehandler. + +```go +// File: cosmos-sdk/examples/basecoin/app/app.go +app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) +``` + ### Handling Fee payment ### Handling Authentication +The antehandler is responsible for handling all authentication of a transaction before passing the message onto its handler. +This generally involves signature verification. The antehandler should check that all of the addresses that are returned in +`tx.GetMsg().GetSigners()` signed the message and that they signed over `tx.GetMsg().GetSignBytes()`. + ## Accounts and x/auth -### sdk.Account +### auth.Account + +```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 +} +``` + +Accounts are the standard way for an application to keep track of addresses and their associated balances. + ### auth.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"` +} +``` + +The `auth.BaseAccount` struct provides a standard implementation of the Account interface with replay protection. +BaseAccount can be extended by embedding it in your own Account struct. + ### auth.AccountMapper +```go +// This AccountMapper encodes/decodes accounts using the +// go-amino (binary) encoding/decoding library. +type AccountMapper struct { + + // The (unexposed) key used to access the store from the Context. + key sdk.StoreKey + + // The prototypical Account concrete type. + proto Account + + // The wire codec for binary encoding/decoding of accounts. + cdc *wire.Codec +} +``` + +The AccountMapper is responsible for managing and storing the state of all accounts in the application. + +Example Initialization: + +```go +// File: examples/basecoin/app/app.go +// Define the accountMapper. +app.accountMapper = auth.NewAccountMapper( + cdc, + app.keyAccount, // target store + &types.AppAccount{}, // prototype +) +``` + +The accountMapper allows you to retrieve the current account state by `GetAccount(ctx Context, addr auth.Address)` and change the state by +`SetAccount(ctx Context, acc Account)`. + +Note: To update an account you will first have to get the account, update the appropriate fields with its associated setter method, and then call +`SetAccount(ctx Context, acc updatedAccount)`. + +Updating accounts is made easier by using the `Keeper` struct in the `x/bank` module. + +Example Initialization: + +```go +// File: examples/basecoin/app/app.go +app.coinKeeper = bank.NewKeeper(app.accountMapper) +``` + +Example Usage: + +```go +// Finds account with addr in accountmapper +// Adds coins to account's coin array +// Sets updated account in accountmapper +app.coinKeeper.AddCoins(ctx, addr, coins) +``` + ## Wire codec ### Why another codec? diff --git a/types/store.go b/types/store.go index 2bd34bebd8..7885d7ab64 100644 --- a/types/store.go +++ b/types/store.go @@ -105,12 +105,11 @@ type KVStore interface { // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. - // CONTRACT: No writes may happen within a domain while an iterator exists over it. + // To iterate over entire domain -> store.Iterator(nil, nil) Iterator(start, end []byte) Iterator // Iterator over a domain of keys in descending order. End is exclusive. // Start must be greater than end, or the Iterator is invalid. - // CONTRACT: No writes may happen within a domain while an iterator exists over it. ReverseIterator(start, end []byte) Iterator // TODO Not yet implemented. From a77ac582a36539df695eb7b5b09f1c193a472966 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sat, 16 Jun 2018 17:45:48 -0700 Subject: [PATCH 2/2] note on iterators --- types/store.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/types/store.go b/types/store.go index 7885d7ab64..d0a8df07ab 100644 --- a/types/store.go +++ b/types/store.go @@ -105,11 +105,15 @@ type KVStore interface { // Iterator over a domain of keys in ascending order. End is exclusive. // Start must be less than end, or the Iterator is invalid. - // To iterate over entire domain -> store.Iterator(nil, nil) + // Iterator must be closed by caller. + // To iterate over entire domain, use store.Iterator(nil, nil) + // CONTRACT: No writes may happen within a domain while an iterator exists over it. Iterator(start, end []byte) Iterator // Iterator over a domain of keys in descending order. End is exclusive. // Start must be greater than end, or the Iterator is invalid. + // Iterator must be closed by caller. + // CONTRACT: No writes may happen within a domain while an iterator exists over it. ReverseIterator(start, end []byte) Iterator // TODO Not yet implemented.