doc update

This commit is contained in:
rigelrozanski 2017-04-01 15:49:56 -04:00 committed by Ethan Buchman
parent 18f1142d4c
commit 02b4fd5f17
4 changed files with 99 additions and 110 deletions

View File

@ -31,6 +31,7 @@ var (
func newKeyCmd(cmd *cobra.Command, args []string) {
key := genKey()
keyJSON, err := json.MarshalIndent(key, "", "\t")
fmt.Println(&key)
if err != nil {
cmn.Exit(fmt.Sprintf("%+v\n", err))
}

View File

@ -36,18 +36,20 @@ var (
}
)
//flags
var (
//persistent flags
txNodeFlag string
toFlag string
amountFlag string
fromFlag string
seqFlag int
gasFlag int
feeFlag string
dataFlag string
nameFlag string
chainIDFlag string
//non-persistent flags
toFlag string
dataFlag string
nameFlag string
)
func init() {

View File

@ -6,10 +6,11 @@ Here, we will demonstrate how to extend the blockchain and CLI to support a simp
## Overview
Creating a new plugin and CLI to support it requires a little bit of boilerplate, but not much.
For convenience, we've implemented an extremely simple example plugin that can be easily modified.
The example is under `docs/guide/src/example-plugin`.
To build your own plugin, copy this folder to a new location and start modifying it there.
Creating a new plugin and CLI to support it requires a little bit of
boilerplate, but not much. For convenience, we've implemented an extremely
simple example plugin that can be easily modified. The example is under
`docs/guide/src/example-plugin`. To build your own plugin, copy this folder to
a new location and start modifying it there.
Let's take a look at the files in `docs/guide/src/example-plugin`:
@ -52,16 +53,14 @@ func main() {
}
```
It creates the CLI, exactly like the `basecoin` one.
However, if we want our plugin to be active,
we need to make sure it is registered with the application.
In addition, if we want to send transactions to our plugin,
we need to add a new command to the CLI.
This is where the `cmd.go` comes in.
It creates the CLI, exactly like the `basecoin` one. However, if we want our
plugin to be active, we need to make sure it is registered with the
application. In addition, if we want to send transactions to our plugin, we
need to add a new command to the CLI. This is where the `cmd.go` comes in.
### cmd.go
First we define the new command and associated flag variables
First we define the new CLI command and associated flag variables.
```golang
var (
@ -77,7 +76,10 @@ var (
)
```
Next we register the plugin:
Next within the `init` function we register our plugin's flags and register our
custom plugin command with the root command. This creates a new subcommand
under `tx` (defined below), and ensures the plugin is activated when we start
the app.
```golang
func init() {
@ -93,59 +95,38 @@ func init() {
}
```
This creates a new subcommand under `tx` (defined below),
and ensures the plugin is activated when we start the app.
We now define the actual function which is called by our CLI command.
```golang
func examplePluginTxCmd(cmd *cobra.Command, args []string) {
exampleTx := ExamplePluginTx{validFlag}
exampleTxBytes := wire.BinaryBytes(exampleTx)
commands.AppTx("example-plugin", exampleTxBytes)
}
```
Our function is a simple command with one boolean flag. However, it actually
inherits the persistent flags from the Basecoin framework. These persistent
flags use pointers to these variables stored in `cmd/commands/tx.go`:
```golang
var (
ExampleFlag = cli.BoolFlag{
Name: "valid",
Usage: "Set this to make the transaction valid",
}
ExamplePluginTxCmd = cli.Command{
Name: "example",
Usage: "Create, sign, and broadcast a transaction to the example plugin",
Action: func(c *cli.Context) error {
return cmdExamplePluginTx(c)
},
Flags: append(commands.TxFlags, ExampleFlag),
}
//persistent flags
txNodeFlag string
amountFlag string
fromFlag string
seqFlag int
gasFlag int
feeFlag string
chainIDFlag string
//non-persistent flags
toFlag string
dataFlag string
nameFlag string
)
func cmdExamplePluginTx(c *cli.Context) error {
exampleFlag := c.Bool("valid")
exampleTx := ExamplePluginTx{exampleFlag}
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
}
```
It's a simple command with one flag, which is just a boolean.
However, it actually inherits more flags from the Basecoin framework:
```golang
Flags: append(commands.TxFlags, ExampleFlag),
```
The `commands.TxFlags` is defined in `cmd/commands/tx.go`:
```golang
var TxFlags = []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
}
```
It adds all the default flags for a Basecoin transaction.
If we now compile and run our program, we can see all the options:
```
@ -157,48 +138,51 @@ example-plugin tx example --help
The output:
```
NAME:
example-plugin tx example - Create, sign, and broadcast a transaction to the example plugin
Create, sign, and broadcast a transaction to the example plugin
USAGE:
example-plugin tx example [command options] [arguments...]
Usage:
example-plugin tx example [flags]
OPTIONS:
--node value Tendermint RPC address (default: "tcp://localhost:46657")
--chain_id value ID of the chain for replay protection (default: "test_chain_id")
--from value Path to a private key to sign the transaction (default: "key.json")
--amount value Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver)
--gas value The amount of gas for the transaction (default: 0)
--fee value Coins for the transaction fee of the format <amt><coin>
--sequence value Sequence number for the account (default: 0)
--valid Set this to make the transaction valid
Flags:
--valid Set this to make transaction valid
Global Flags:
--amount string Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver},
--chain_id string ID of the chain for replay protection (default "test_chain_id")
--fee string Coins for the transaction fee of the format <amt><coin>
--from string Path to a private key to sign the transaction (default "key.json")
--gas int The amount of gas for the transaction
--node string Tendermint RPC address (default "tcp://localhost:46657")
--sequence int Sequence number for the account (-1 to autocalculate}, (default -1)
```
Cool, eh?
Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`:
Before we move on to `plugin.go`, let's look at the `examplePluginTxCmd`
function in `cmd.go`:
```golang
func cmdExamplePluginTx(c *cli.Context) error {
exampleFlag := c.Bool("valid")
exampleTx := ExamplePluginTx{exampleFlag}
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
func examplePluginTxCmd(cmd *cobra.Command, args []string) {
exampleTx := ExamplePluginTx{validFlag}
exampleTxBytes := wire.BinaryBytes(exampleTx)
commands.AppTx("example-plugin", exampleTxBytes)
}
```
We read the flag from the CLI library, and then create the example transaction.
Remember that Basecoin itself only knows about two transaction types, `SendTx` and `AppTx`.
All plugin data must be serialized (ie. encoded as a byte-array)
and sent as data in an `AppTx`. The `commands.AppTx` function does this for us -
it creates an `AppTx` with the corresponding data, signs it, and sends it on to the blockchain.
Remember that Basecoin itself only knows about two transaction types, `SendTx`
and `AppTx`. All plugin data must be serialized (ie. encoded as a byte-array)
and sent as data in an `AppTx`. The `commands.AppTx` function does this for us
- it creates an `AppTx` with the corresponding data, signs it, and sends it on
to the blockchain.
### plugin.go
Ok, now we're ready to actually look at the implementation of the plugin in `plugin.go`.
Note I'll leave out some of the methods as they don't serve any purpose for this example,
but are necessary boilerplate.
Your plugin may have additional requirements that utilize these other methods.
Here's what's relevant for us:
Ok, now we're ready to actually look at the implementation of the plugin in
`plugin.go`. Note I'll leave out some of the methods as they don't serve any
purpose for this example, but are necessary boilerplate. Your plugin may have
additional requirements that utilize these other methods. Here's what's
relevant for us:
```golang
type ExamplePluginState struct {
@ -232,15 +216,17 @@ func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string
}
func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
// Decode txBytes using go-wire. Attempt to write the txBytes to the variable
// tx, if the txBytes have not been properly encoded from a ExamplePluginTx
// struct wire will produce an error.
var tx ExamplePluginTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
}
// Validate tx
// Perform Transaction Validation
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
}
@ -248,8 +234,10 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
// Load PluginState
var pluginState ExamplePluginState
stateBytes := store.Get(ep.StateKey())
// If the state does not exist, stateBytes will be initialized
// as an empty byte array with length of zero
if len(stateBytes) > 0 {
err = wire.ReadBinaryBytes(stateBytes, &pluginState)
err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire
if err != nil {
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
}
@ -266,12 +254,11 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
```
All we're doing here is defining a state and transaction type for our plugin,
and then using the `RunTx` method to define how the transaction updates the state.
Let's break down `RunTx` in parts. First, we deserialize the transaction:
and then using the `RunTx` method to define how the transaction updates the
state. Let's break down `RunTx` in parts. First, we deserialize the
transaction:
```golang
// Decode tx
var tx ExamplePluginTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
@ -279,27 +266,26 @@ if err != nil {
}
```
The transaction is expected to be serialized according to Tendermint's "wire" format,
as defined in the `github.com/tendermint/go-wire` package.
If it's not encoded properly, we return an error.
The transaction is expected to be serialized according to Tendermint's "wire"
format, as defined in the `github.com/tendermint/go-wire` package. If it's not
encoded properly, we return an error.
If the transaction deserializes correctly, we can now check if it's valid:
```golang
// Validate tx
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
}
```
The transaction is valid if the `Valid` field is set, otherwise it's not - simple as that.
Finally, we can update the state. In this example, the state simply counts how many valid transactions
we've processed. But the state itself is serialized and kept in some `store`, which is typically a Merkle tree.
So first we have to load the state from the store and deserialize it:
The transaction is valid if the `Valid` field is set, otherwise it's not -
simple as that. Finally, we can update the state. In this example, the state
simply counts how many valid transactions we've processed. But the state itself
is serialized and kept in some `store`, which is typically a Merkle tree. So
first we have to load the state from the store and deserialize it:
```golang
// Load PluginState
var pluginState ExamplePluginState
stateBytes := store.Get(ep.StateKey())
if len(stateBytes) > 0 {
@ -402,5 +388,5 @@ basecoin CLI to activate the plugin on the blockchain and to send transactions t
Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them.
In the [next tutorial](more-examples.md), we tour through some other plugin examples,
adding features for minting new coins, voting, and changing the Tendermint validator set.
addin mple-plugin query ExamplePlugin.Statefeatures for minting new coins, voting, and changing the Tendermint validator set.
But first, you may want to learn a bit more about [the design of the plugin system](plugin-design.md)

View File

@ -43,8 +43,8 @@ type Plugin interface {
// Other ABCI message handlers
SetOption(store KVStore, key string, value string) (log string)
InitChain(store KVStore, vals []*abci.Validator)
BeginBlock(store KVStore, height uint64)
EndBlock(store KVStore, height uint64) []*abci.Validator
BeginBlock(store KVStore, hash []byte, header *abci.Header)
EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock)
}
type CallContext struct {