some reordering in app1 and app3
This commit is contained in:
parent
dde8abf9d7
commit
72c1381a4d
|
@ -288,6 +288,52 @@ The handler is straight forward:
|
||||||
|
|
||||||
And that's that!
|
And that's that!
|
||||||
|
|
||||||
|
# Tx
|
||||||
|
|
||||||
|
The final piece before putting it all together is the `Tx`.
|
||||||
|
While `Msg` contains the content for particular functionality in the application, the actual input
|
||||||
|
provided by the user is a serialized `Tx`. Applications may have many implementations of the `Msg` interface,
|
||||||
|
but they should have only a single implementation of `Tx`:
|
||||||
|
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Transactions wrap messages.
|
||||||
|
type Tx interface {
|
||||||
|
// Gets the Msgs.
|
||||||
|
GetMsgs() []Msg
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `Tx` just wraps a `[]Msg`, and may include additional authentication data, like signatures and account nonces.
|
||||||
|
Applications must specify how their `Tx` is decoded, as this is the ultimate input into the application.
|
||||||
|
We'll talk more about `Tx` types later, specifically when we introduce the `StdTx`.
|
||||||
|
|
||||||
|
For this example, we have a dead-simple `Tx` type that contains the `MsgSend` and is JSON decoded:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Simple tx to wrap the Msg.
|
||||||
|
type app1Tx struct {
|
||||||
|
MsgSend
|
||||||
|
}
|
||||||
|
|
||||||
|
// This tx only has one Msg.
|
||||||
|
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
||||||
|
return []sdk.Msg{tx.MsgSend}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON decode MsgSend.
|
||||||
|
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
||||||
|
var tx app1Tx
|
||||||
|
err := json.Unmarshal(txBytes, &tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, sdk.ErrTxDecode(err.Error())
|
||||||
|
}
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Thus, transactions in this blockchain are expected to be JSON encoded `MsgSend`.
|
||||||
|
|
||||||
# BaseApp
|
# BaseApp
|
||||||
|
|
||||||
Finally, we stitch it all together using the `BaseApp`.
|
Finally, we stitch it all together using the `BaseApp`.
|
||||||
|
@ -341,50 +387,9 @@ the logger, and the filesystem later in the tutorial. For now, note that this is
|
||||||
Here, we have only one store and one handler, and the handler is granted access by giving it the capability key.
|
Here, we have only one store and one handler, and the handler is granted access by giving it the capability key.
|
||||||
In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store.
|
In future apps, we'll have multiple stores and handlers, and not every handler will get access to every store.
|
||||||
|
|
||||||
Note also the call to `SetTxDecoder`. While `Msg` contains the content for particular functionality in the application, the actual input
|
After setting the transaction decoder and the message handling routes, the final
|
||||||
provided by the user is a serialized `Tx`. Applications may have many implementations of the `Msg` interface, but they should have only
|
step is to mount the stores and load the latest version.
|
||||||
a single implementation of `Tx`:
|
Since we only have one store, we only mount one.
|
||||||
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Transactions wrap messages.
|
|
||||||
type Tx interface {
|
|
||||||
// Gets the Msgs.
|
|
||||||
GetMsgs() []Msg
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `Tx` just wraps a `[]Msg`, and may include additional authentication data, like signatures and account nonces.
|
|
||||||
Applications must specify how their `Tx` is decoded, as this is the ultimate input into the application.
|
|
||||||
We'll talk more about `Tx` types later in the tutorial, specifically when we introduce the `StdTx`.
|
|
||||||
|
|
||||||
For this example, we have a dead-simple `Tx` type that contains the `MsgSend` and is JSON decoded:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Simple tx to wrap the Msg.
|
|
||||||
type app1Tx struct {
|
|
||||||
MsgSend
|
|
||||||
}
|
|
||||||
|
|
||||||
// This tx only has one Msg.
|
|
||||||
func (tx app1Tx) GetMsgs() []sdk.Msg {
|
|
||||||
return []sdk.Msg{tx.MsgSend}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JSON decode MsgSend.
|
|
||||||
func txDecoder(txBytes []byte) (sdk.Tx, sdk.Error) {
|
|
||||||
var tx app1Tx
|
|
||||||
err := json.Unmarshal(txBytes, &tx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, sdk.ErrTxDecode(err.Error())
|
|
||||||
}
|
|
||||||
return tx, nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This means the input to the app must be a JSON encoded `app1Tx`.
|
|
||||||
|
|
||||||
The last step in `NewApp1` is to mount the stores and load the latest version. Since we only have one store, we only mount one.
|
|
||||||
|
|
||||||
## Conclusion
|
## Conclusion
|
||||||
|
|
||||||
|
|
|
@ -5,54 +5,127 @@ transactions. In that example, our `Tx` implementation was still just a simple
|
||||||
wrapper of the `Msg`, providing no actual authentication. Here, in `App3`, we
|
wrapper of the `Msg`, providing no actual authentication. Here, in `App3`, we
|
||||||
expand on `App2` to provide real authentication in the transactions.
|
expand on `App2` to provide real authentication in the transactions.
|
||||||
|
|
||||||
|
Without loss of generality, the SDK prescribes native
|
||||||
|
account and transaction types that are sufficient for a wide range of applications.
|
||||||
|
These are implemented in the `x/auth` module, where
|
||||||
|
all authentication related data structures and logic reside.
|
||||||
|
Applications that use `x/auth` don't need to worry about any of the details of
|
||||||
|
authentication and replay protection, as they are handled automatically. For
|
||||||
|
completeness, we will explain everything here.
|
||||||
|
|
||||||
|
## Account
|
||||||
|
|
||||||
|
The `Account` interface provides a model of accounts that have:
|
||||||
|
|
||||||
|
- Address for identification
|
||||||
|
- PubKey for authentication
|
||||||
|
- AccountNumber to prune empty accounts
|
||||||
|
- Sequence to prevent transaction replays
|
||||||
|
- Coins to carry a balance
|
||||||
|
|
||||||
|
It consists of getters and setters for each of these:
|
||||||
|
|
||||||
|
```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
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## BaseAccount
|
||||||
|
|
||||||
|
The default implementation of `Account` is the `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"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## StdTx
|
## StdTx
|
||||||
|
|
||||||
The standard way to create a transaction from a message is to use the `StdTx` struct defined in the `x/auth` module.
|
The standard way to create a transaction from a message is to use the `StdTx` struct defined in the `x/auth` module:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// 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 {
|
type StdTx struct {
|
||||||
Msg sdk.Msg `json:"msg"`
|
Msgs []sdk.Msg `json:"msg"`
|
||||||
Fee StdFee `json:"fee"`
|
Fee StdFee `json:"fee"`
|
||||||
Signatures []StdSignature `json:"signatures"`
|
Signatures []StdSignature `json:"signatures"`
|
||||||
|
Memo string `json:"memo"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
The `StdTx.GetSignatures()` method returns a list of signatures, which must match
|
The `StdTx` includes a list of messages, information about the fee being paid,
|
||||||
the list of addresses returned by `tx.Msg.GetSigners()`. The signatures come in
|
and a list of signatures. It also includes an optional `Memo` for additional
|
||||||
a standard form:
|
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`:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
// 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 {
|
type StdSignature struct {
|
||||||
crypto.PubKey // optional
|
crypto.PubKey `json:"pub_key"` // optional
|
||||||
crypto.Signature
|
crypto.Signature `json:"signature"`
|
||||||
AccountNumber int64
|
AccountNumber int64 `json:"account_number"`
|
||||||
Sequence int64
|
Sequence int64 `json:"sequence"`
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
It contains the signature itself, as well as the corresponding account's
|
Recall that the `Sequence` is expected to increment every time a
|
||||||
sequence number. The sequence number is expected to increment every time a
|
message is signed by a given account in order to prevent "replay attacks" where
|
||||||
message is signed by a given account. This prevents "replay attacks", where
|
the same message could be executed over and over again. The `AccountNumber` is
|
||||||
the same message could be executed over and over again.
|
assigned when the account is created or recreated after being emptied.
|
||||||
|
|
||||||
The `StdSignature` can also optionally include the public key for verifying the
|
The `StdSignature` can also optionally include the public key for verifying the
|
||||||
signature. An application can store the public key for each address it knows
|
signature. The public key only needs to be included the first time a transaction
|
||||||
about, making it optional to include the public key in the transaction. In the
|
is sent from a given account - from then on it will be stored in the `Account`
|
||||||
case of Basecoin, the public key only needs to be included in the first
|
and can be left out of transactions.
|
||||||
transaction send by a given account - after that, the public key is forever
|
|
||||||
stored by the application and can be left out of transactions.
|
|
||||||
|
|
||||||
The address responsible for paying the transactions fee is the first address
|
The fee is provided in a standard form as `StdFee`:
|
||||||
returned by msg.GetSigners(). The convenience function `FeePayer(tx Tx)` is provided
|
|
||||||
to return this.
|
|
||||||
|
|
||||||
The standard bytes for signers to sign over is provided by:
|
|
||||||
|
|
||||||
```go
|
|
||||||
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
|
```go
|
||||||
// StdFee includes the amount of coins paid in fees and the maximum
|
// StdFee includes the amount of coins paid in fees and the maximum
|
||||||
|
@ -64,6 +137,18 @@ type StdFee struct {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```go
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
## AnteHandler
|
## AnteHandler
|
||||||
|
|
||||||
The AnteHandler is used to do all transaction-level processing (i.e. Fee payment, signature verification)
|
The AnteHandler is used to do all transaction-level processing (i.e. Fee payment, signature verification)
|
||||||
|
@ -101,47 +186,6 @@ This generally involves signature verification. The antehandler should check tha
|
||||||
|
|
||||||
### auth.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
|
### auth.AccountMapper
|
||||||
|
|
||||||
```go
|
```go
|
||||||
|
|
Loading…
Reference in New Issue