doc update
This commit is contained in:
parent
18f1142d4c
commit
02b4fd5f17
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -36,18 +36,20 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
//flags
|
||||
var (
|
||||
//persistent flags
|
||||
txNodeFlag string
|
||||
toFlag string
|
||||
amountFlag string
|
||||
fromFlag string
|
||||
seqFlag int
|
||||
gasFlag int
|
||||
feeFlag string
|
||||
chainIDFlag string
|
||||
|
||||
//non-persistent flags
|
||||
toFlag string
|
||||
dataFlag string
|
||||
nameFlag string
|
||||
chainIDFlag string
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
//persistent flags
|
||||
txNodeFlag string
|
||||
amountFlag string
|
||||
fromFlag string
|
||||
seqFlag int
|
||||
gasFlag int
|
||||
feeFlag string
|
||||
chainIDFlag string
|
||||
|
||||
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),
|
||||
}
|
||||
//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 {
|
||||
|
@ -233,14 +217,16 @@ 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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue