guide refactor
This commit is contained in:
parent
6af6e6a4a1
commit
0f45f02dac
|
@ -1,7 +1,8 @@
|
|||
# Basecoin Basics
|
||||
|
||||
Here we explain how to get started with a simple Basecoin blockchain,
|
||||
and how to send transactions between accounts using the `basecoin` tool.
|
||||
how to send transactions between accounts using the `basecoin` tool,
|
||||
and what is happening under the hood.
|
||||
|
||||
## Install
|
||||
|
||||
|
@ -80,14 +81,93 @@ basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.jso
|
|||
|
||||
See `basecoin tx send --help` for additional details.
|
||||
|
||||
## Plugins
|
||||
For a better understanding of the options, it helps to understand the underlying data structures.
|
||||
|
||||
The `tx send` command creates and broadcasts a transaction of type `SendTx`,
|
||||
which is only useful for moving tokens around.
|
||||
Fortunately, Basecoin supports another transaction type, the `AppTx`,
|
||||
which can trigger code registered via a plugin system.
|
||||
## Accounts
|
||||
|
||||
In the [next tutorial](example-plugin.md),
|
||||
we demonstrate how to implement a plugin
|
||||
and extend the CLI to support new transaction types!
|
||||
But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md)
|
||||
The Basecoin state consists entirely of a set of accounts.
|
||||
Each account contains an ED25519 public key,
|
||||
a balance in many different coin denominations,
|
||||
and a strictly increasing sequence number for replay protection.
|
||||
This type of account was directly inspired by accounts in Ethereum,
|
||||
and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs).
|
||||
Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens.
|
||||
|
||||
```golang
|
||||
type Account struct {
|
||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
||||
Sequence int `json:"sequence"`
|
||||
Balance Coins `json:"coins"`
|
||||
}
|
||||
|
||||
type Coins []Coin
|
||||
|
||||
type Coin struct {
|
||||
Denom string `json:"denom"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
```
|
||||
|
||||
Accounts are serialized and stored in a Merkle tree under the key `base/a/<address>`, where `<address>` is the address of the account.
|
||||
Typically, the address of the account is the 20-byte `RIPEMD160` hash of the public key, but other formats are acceptable as well,
|
||||
as defined in the [tendermint crypto library](https://github.com/tendermint/go-crypto).
|
||||
The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle).
|
||||
|
||||
## Transactions
|
||||
|
||||
Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts.
|
||||
The `SendTx` takes a list of inputs and a list of outputs,
|
||||
and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output.
|
||||
The `SendTx` is structured as follows:
|
||||
|
||||
```golang
|
||||
type SendTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Inputs []TxInput `json:"inputs"`
|
||||
Outputs []TxOutput `json:"outputs"`
|
||||
}
|
||||
|
||||
type TxInput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
||||
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||
PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
|
||||
}
|
||||
|
||||
type TxOutput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
}
|
||||
```
|
||||
|
||||
Note the `SendTx` includes a field for `Gas` and `Fee`.
|
||||
The `Gas` limits the total amount of computation that can be done by the transaction,
|
||||
while the `Fee` refers to the total amount paid in fees.
|
||||
This is slightly different from Ethereum's concept of `Gas` and `GasPrice`,
|
||||
where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent,
|
||||
and the `GasPrice` is implicit.
|
||||
|
||||
In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering
|
||||
of transactions, like in bitcoin. And the `Gas` is meant to be used by the application
|
||||
plugin to control its execution. There is currently no means to pass `Fee` information
|
||||
to the Tendermint validators, but it will come soon...
|
||||
|
||||
Note also that the `PubKey` only needs to be sent for `Sequence == 0`.
|
||||
After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it,
|
||||
using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions
|
||||
as it uses a different elliptic curve scheme which enables the public key to be derived from the signature itself.
|
||||
|
||||
Finally, note that the use of multiple inputs and multiple outputs allows us to send many
|
||||
different types of tokens between many different accounts at once in an atomic transaction.
|
||||
Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple
|
||||
inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of
|
||||
coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction.
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this guide, we introduced the `basecoin` tool, demonstrated how to use it to send tokens between accounts,
|
||||
and discussed the underlying data types for accounts and transactions, specifically the `Account` and the `SendTx`.
|
||||
In the [next guide](basecoin-plugins.md), we introduce the basecoin plugin system, which uses a new transaction type, the `AppTx`,
|
||||
to extend the functionality of the Basecoin system with arbitrary logic.
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
# Basecoin Design
|
||||
|
||||
Basecoin is designed to be a simple cryptocurrency application with limited built-in functionality,
|
||||
but with the capacity to be extended by arbitrary plugins.
|
||||
Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built-in virtual machine.
|
||||
|
||||
## Accounts
|
||||
|
||||
The Basecoin state consists entirely of a set of accounts.
|
||||
Each account contains an ED25519 public key,
|
||||
a balance in many different coin denominations,
|
||||
and a strictly increasing sequence number for replay protection.
|
||||
This type of account was directly inspired by accounts in Ethereum,
|
||||
and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs).
|
||||
Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens.
|
||||
|
||||
```golang
|
||||
type Account struct {
|
||||
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
|
||||
Sequence int `json:"sequence"`
|
||||
Balance Coins `json:"coins"`
|
||||
}
|
||||
|
||||
type Coins []Coin
|
||||
|
||||
type Coin struct {
|
||||
Denom string `json:"denom"`
|
||||
Amount int64 `json:"amount"`
|
||||
}
|
||||
```
|
||||
|
||||
Accounts are serialized and stored in a Merkle tree using the account's address as the key,
|
||||
In particular, an account is stored in the Merkle tree under the key `base/a/<address>`,
|
||||
where `<address>` is the address of the account.
|
||||
In Basecoin, the address of an account is the 20-byte `RIPEMD160` hash of the public key.
|
||||
The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle).
|
||||
|
||||
## Transactions
|
||||
|
||||
Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts.
|
||||
The `SendTx` takes a list of inputs and a list of outputs,
|
||||
and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output.
|
||||
The `SendTx` is structured as follows:
|
||||
|
||||
```golang
|
||||
type SendTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Inputs []TxInput `json:"inputs"`
|
||||
Outputs []TxOutput `json:"outputs"`
|
||||
}
|
||||
|
||||
type TxInput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput
|
||||
Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
|
||||
PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
|
||||
}
|
||||
|
||||
type TxOutput struct {
|
||||
Address []byte `json:"address"` // Hash of the PubKey
|
||||
Coins Coins `json:"coins"` //
|
||||
}
|
||||
```
|
||||
|
||||
There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`.
|
||||
The `Gas` limits the total amount of computation that can be done by the transaction,
|
||||
while the `Fee` refers to the total amount paid in fees.
|
||||
This is slightly different from Ethereum's concept of `Gas` and `GasPrice`,
|
||||
where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent,
|
||||
and the `GasPrice` is implicit.
|
||||
|
||||
In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering
|
||||
of transactions, like in bitcoin. And the `Gas` is meant to be used by the application
|
||||
plugin to control its execution. There is currently no means to pass `Fee` information
|
||||
to the Tendermint validators, but it will come soon...
|
||||
|
||||
Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`.
|
||||
After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it,
|
||||
using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions
|
||||
as it uses a different elliptic curve scheme which enables the public key to be derived from the signature itself.
|
||||
|
||||
Finally, note that the use of multiple inputs and multiple outputs allows us to send many
|
||||
different types of tokens between many different accounts at once in an atomic transaction.
|
||||
Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple
|
||||
inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of
|
||||
coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction.
|
||||
|
||||
## Plugins
|
||||
|
||||
Basecoin actually defines a second transaction type, the `AppTx`,
|
||||
which enables the functionality to be extended via custom plugins.
|
||||
To learn more about the `AppTx` and plugin system, see the [plugin design document](plugin-design.md).
|
||||
To implement your first plugin, see [plugin tutorial](example-plugin.md).
|
|
@ -0,0 +1,162 @@
|
|||
# Basecoin Plugins
|
||||
|
||||
In the [previous guide](basecoin-basics.md),
|
||||
we saw how to use the `basecoin` tool to start a blockchain and send transactions.
|
||||
We also learned about `Account` and `SendTx`, the basic data types giving us a multi-asset cryptocurrency.
|
||||
Here, we will demonstrate how to extend the `basecoin` tool to use another transaction type, the `AppTx`,
|
||||
to send data to a custom plugin. In this case we use a simple plugin that takes a single boolean argument,
|
||||
and only accept the transaction if the argument is set to `true`.
|
||||
|
||||
## Example Plugin
|
||||
|
||||
The design of the `basecoin` tool makes it easy to extend for custom functionality.
|
||||
To see what this looks like, install the `example-plugin` tool:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||
go install ./docs/guide/src/example-plugin
|
||||
```
|
||||
|
||||
The `example-plugin` tool is just like the `basecoin` tool.
|
||||
They both use the same library of commands, including one for signing and broadcasting `SendTx`.
|
||||
See `example-plugin --help` for details.
|
||||
|
||||
A new blockchain can be initialized and started just like with `basecoin`:
|
||||
|
||||
```
|
||||
example-plugin init
|
||||
example-plugin start
|
||||
```
|
||||
|
||||
The default files are stored in `~/.basecoin-example-plugin`.
|
||||
In another window, we can send a `SendTx` like we are used to:
|
||||
|
||||
```
|
||||
example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 1mycoin
|
||||
```
|
||||
|
||||
But the `example-plugin` tool has an additional command, `example-plugin tx example`,
|
||||
which crafts an `AppTx` specifically for our example plugin.
|
||||
This command lets you send a single boolean argument:
|
||||
|
||||
```
|
||||
example-plugin tx example --amount 1mycoin
|
||||
example-plugin tx example --amount 1mycoin --valid
|
||||
```
|
||||
|
||||
The first transaction is rejected by the plugin because it was not marked as valid, while the second transaction passes.
|
||||
We can build plugins that take many arguments of different types, and easily extend the tool to accomodate them.
|
||||
Of course, we can also expose queries on our plugin:
|
||||
|
||||
```
|
||||
example-plugin query ExamplePlugin.State
|
||||
```
|
||||
|
||||
Note the `"value":"0101"`. This is the serialized form of the state,
|
||||
which contains only an integer, the number of valid transactions.
|
||||
If we send another transaction, and then query again, we will see the value increment:
|
||||
|
||||
```
|
||||
example-plugin tx example --valid --amount 1mycoin
|
||||
example-plugin query ExamplePlugin.State
|
||||
```
|
||||
|
||||
The value should now be `0102`, because we sent a second valid transaction.
|
||||
Notice how the result of the query comes with a proof.
|
||||
This is a Merkle proof that the state is what we say it is.
|
||||
In a latter [guide on InterBlockchain Communication](ibc.md),
|
||||
we'll put this proof to work!
|
||||
|
||||
|
||||
Now, before we implement our own plugin and tooling, it helps to understand the `AppTx` and the design of the plugin system.
|
||||
|
||||
## AppTx
|
||||
|
||||
The `AppTx` is similar to the `SendTx`, but instead of sending coins from inputs to outputs,
|
||||
it sends coins from one input to a plugin, and can also send some data.
|
||||
|
||||
```golang
|
||||
type AppTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Input TxInput `json:"input"`
|
||||
Name string `json:"type"` // Name of the plugin
|
||||
Data []byte `json:"data"` // Data for the plugin to process
|
||||
}
|
||||
```
|
||||
|
||||
The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins.
|
||||
The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction,
|
||||
and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing.
|
||||
|
||||
Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`.
|
||||
It also includes a single `TxInput`, which specifies the sender of the transaction,
|
||||
and some coins that can be forwarded to the plugin as well.
|
||||
|
||||
## Plugins
|
||||
|
||||
A plugin is simply a Go package that implements the `Plugin` interface:
|
||||
|
||||
```golang
|
||||
type Plugin interface {
|
||||
|
||||
// Name of this plugin, should be short.
|
||||
Name() string
|
||||
|
||||
// Run a transaction from ABCI DeliverTx
|
||||
RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result)
|
||||
|
||||
// Other ABCI message handlers
|
||||
SetOption(store KVStore, key string, value string) (log string)
|
||||
InitChain(store KVStore, vals []*abci.Validator)
|
||||
BeginBlock(store KVStore, hash []byte, header *abci.Header)
|
||||
EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock)
|
||||
}
|
||||
|
||||
type CallContext struct {
|
||||
CallerAddress []byte // Caller's Address (hash of PubKey)
|
||||
CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted
|
||||
Coins Coins // The coins that the caller wishes to spend, excluding fees
|
||||
}
|
||||
```
|
||||
|
||||
The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed.
|
||||
The `Data` from the `AppTx` is passed in as the `txBytes`,
|
||||
while the `Input` from the `AppTx` is used to populate the `CallContext`.
|
||||
|
||||
Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data.
|
||||
By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly,
|
||||
and also to store arbitrary other information in the state.
|
||||
In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended.
|
||||
One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin!
|
||||
|
||||
For details on how to initialize the state using `SetOption`, see the [guide to using the basecoin tool](basecoin-tool.md#genesis).
|
||||
|
||||
|
||||
## Implement your own
|
||||
|
||||
To implement your own plugin and tooling, make a copy of `docs/guide/src/example-plugin`,
|
||||
and modify the code accordingly. Here, we will briefly describe the design and the changes to be made,
|
||||
but see the code for more details.
|
||||
|
||||
First is the `main.go`, which drives the program. It can be left alone, but you should change any occurences of `example-plugin`
|
||||
to whatever your plugin tool is going to be called.
|
||||
|
||||
Next is the `cmd.go`. This is where we extend the tool with any new commands and flags we need to send transactions to our plugin.
|
||||
Note the `init()` function, where we register a new transaction subcommand with `RegisterTxSubcommand`,
|
||||
and where we load the plugin into the basecoin app with `RegisterStartPlugin`.
|
||||
|
||||
Finally is the `plugin.go`, where we provide an implementation of the `Plugin` interface.
|
||||
The most important part of the implementation is the `RunTx` method, which determines the meaning of the data
|
||||
sent along in the `AppTx`. In our example, we define a new transaction type, the `ExamplePluginTx`, which
|
||||
we expect to be encoded in the `AppTx.Data`, and thus to be decoded in the `RunTx` method, and used to update the plugin state.
|
||||
|
||||
For more examples and inspiration, see our [repository of example plugins](https://github.com/tendermint/basecoin-examples).
|
||||
|
||||
## Conclusion
|
||||
|
||||
In this guide, we demonstrated how to create a new plugin and how to extend the
|
||||
`basecoin` tool to start a blockchain with the plugin enabled and send transactions to it.
|
||||
In the next guide, we introduce a [plugin for Inter Blockchain Communication](ibc.md),
|
||||
which allows us to publish proofs of the state of one blockchain to another,
|
||||
and thus to transfer tokens and data between them.
|
|
@ -140,3 +140,14 @@ You can reset all blockchain data by running:
|
|||
```
|
||||
basecoin unsafe_reset_all
|
||||
```
|
||||
|
||||
|
||||
# Genesis
|
||||
|
||||
Any required plugin initialization should be constructed using `SetOption` on genesis.
|
||||
When starting a new chain for the first time, `SetOption` will be called for each item the genesis file.
|
||||
Within genesis.json file entries are made in the format: `"<plugin>/<key>", "<value>"`, where `<plugin>` is the plugin name,
|
||||
and `<key>` and `<value>` are the strings passed into the plugin SetOption function.
|
||||
This function is intended to be used to set plugin specific information such
|
||||
as the plugin state.
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
## Deployment
|
||||
|
||||
Up until this point, we have only been testing the code as a blockchain with a single validator node running locally.
|
||||
This is nice for developing, but it's not a real distributed application yet.
|
||||
|
||||
This section will demonstrate how to launch your basecoin-based application along
|
||||
with a tendermint testnet and initialize the genesis block for fun and profit.
|
||||
We do this using the [mintnet-kubernetes tool](https://github.com/tendermint/mintnet-kubernetes).
|
|
@ -1,389 +0,0 @@
|
|||
# Basecoin Example Plugin
|
||||
|
||||
In the [previous tutorial](basecoin-basics.md),
|
||||
we saw how to start a Basecoin blockchain and use the CLI to send transactions.
|
||||
Here, we will demonstrate how to extend the blockchain and CLI to support a simple plugin.
|
||||
|
||||
## 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.
|
||||
|
||||
Let's take a look at the files in `docs/guide/src/example-plugin`:
|
||||
|
||||
```
|
||||
cmd.go
|
||||
main.go
|
||||
plugin.go
|
||||
```
|
||||
|
||||
### main.go
|
||||
|
||||
The `main.go` is very simple and does not need to be changed:
|
||||
|
||||
```golang
|
||||
func main() {
|
||||
//Initialize example-plugin root command
|
||||
var RootCmd = &cobra.Command{
|
||||
Use: "example-plugin",
|
||||
Short: "example-plugin usage description",
|
||||
}
|
||||
|
||||
//Add the default basecoin commands to the root command
|
||||
RootCmd.AddCommand(
|
||||
commands.InitCmd,
|
||||
commands.StartCmd,
|
||||
commands.TxCmd,
|
||||
commands.QueryCmd,
|
||||
commands.KeyCmd,
|
||||
commands.VerifyCmd,
|
||||
commands.BlockCmd,
|
||||
commands.AccountCmd,
|
||||
commands.UnsafeResetAllCmd,
|
||||
)
|
||||
|
||||
//Run the root command
|
||||
commands.ExecuteWithDebug(RootCmd)
|
||||
}
|
||||
```
|
||||
|
||||
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 CLI command and associated flag variables.
|
||||
|
||||
```golang
|
||||
var (
|
||||
//CLI Flags
|
||||
validFlag bool
|
||||
|
||||
//CLI Plugin Commands
|
||||
ExamplePluginTxCmd = &cobra.Command{
|
||||
Use: "example",
|
||||
Short: "Create, sign, and broadcast a transaction to the example plugin",
|
||||
RunE: examplePluginTxCmd,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
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() {
|
||||
|
||||
//Set the Plugin Flags
|
||||
ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid")
|
||||
|
||||
//Register a plugin specific CLI command as a subcommand of the tx command
|
||||
commands.RegisterTxSubcommand(ExamplePluginTxCmd)
|
||||
|
||||
//Register the example with basecoin at start
|
||||
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
|
||||
}
|
||||
```
|
||||
|
||||
We now define the actual function which is called by our CLI command.
|
||||
|
||||
```golang
|
||||
func examplePluginTxCmd(cmd *cobra.Command, args []string) error {
|
||||
exampleTx := ExamplePluginTx{validFlag}
|
||||
exampleTxBytes := wire.BinaryBytes(exampleTx)
|
||||
return 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 (
|
||||
//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
|
||||
)
|
||||
```
|
||||
|
||||
If we now compile and run our program, we can see all the options:
|
||||
|
||||
```
|
||||
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||
go install ./docs/guide/src/example-plugin
|
||||
example-plugin tx example --help
|
||||
```
|
||||
|
||||
The output:
|
||||
|
||||
```
|
||||
Create, sign, and broadcast a transaction to the example plugin
|
||||
|
||||
Usage:
|
||||
example-plugin tx example [flags]
|
||||
|
||||
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 `examplePluginTxCmd`
|
||||
function in `cmd.go`:
|
||||
|
||||
```golang
|
||||
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.
|
||||
|
||||
### 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:
|
||||
|
||||
```golang
|
||||
type ExamplePluginState struct {
|
||||
Counter int
|
||||
}
|
||||
|
||||
type ExamplePluginTx struct {
|
||||
Valid bool
|
||||
}
|
||||
|
||||
type ExamplePlugin struct {
|
||||
name string
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) Name() string {
|
||||
return ep.name
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) StateKey() []byte {
|
||||
return []byte("ExamplePlugin.State")
|
||||
}
|
||||
|
||||
func NewExamplePlugin() *ExamplePlugin {
|
||||
return &ExamplePlugin{
|
||||
name: "example-plugin",
|
||||
}
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
// Perform Transaction Validation
|
||||
if !tx.Valid {
|
||||
return abci.ErrInternalError.AppendLog("Valid must be true")
|
||||
}
|
||||
|
||||
// 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) //decode using go-wire
|
||||
if err != nil {
|
||||
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
//App Logic
|
||||
pluginState.Counter += 1
|
||||
|
||||
// Save PluginState
|
||||
store.Set(ep.StateKey(), wire.BinaryBytes(pluginState))
|
||||
|
||||
return abci.OK
|
||||
}
|
||||
```
|
||||
|
||||
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:
|
||||
|
||||
```golang
|
||||
var tx ExamplePluginTx
|
||||
err := wire.ReadBinaryBytes(txBytes, &tx)
|
||||
if err != nil {
|
||||
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.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
|
||||
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:
|
||||
|
||||
```golang
|
||||
var pluginState ExamplePluginState
|
||||
stateBytes := store.Get(ep.StateKey())
|
||||
if len(stateBytes) > 0 {
|
||||
err = wire.ReadBinaryBytes(stateBytes, &pluginState)
|
||||
if err != nil {
|
||||
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`.
|
||||
Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make
|
||||
use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0.
|
||||
If your app needs more initialization than empty variables, then do this logic here in an `else` block.
|
||||
|
||||
Finally, we can update the state's `Counter`, and save the state back to the store:
|
||||
|
||||
```golang
|
||||
//App Logic
|
||||
pluginState.Counter += 1
|
||||
|
||||
// Save PluginState
|
||||
store.Set(ep.StateKey(), wire.BinaryBytes(pluginState))
|
||||
|
||||
return abci.OK
|
||||
```
|
||||
|
||||
And that's it! Now that we have a simple plugin, let's see how to run it.
|
||||
|
||||
## Running your plugin
|
||||
|
||||
First, initialize the new blockchain with
|
||||
|
||||
```
|
||||
basecoin init
|
||||
```
|
||||
|
||||
If you've already run a basecoin blockchain, reset the data with
|
||||
|
||||
```
|
||||
basecoin unsafe_reset_all
|
||||
```
|
||||
|
||||
To start the blockchain with your new plugin, simply run
|
||||
|
||||
```
|
||||
example-plugin start
|
||||
```
|
||||
|
||||
In another window, we can try sending some transactions:
|
||||
|
||||
```
|
||||
example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 100mycoin
|
||||
```
|
||||
|
||||
Ok, so that's how we can send a `SendTx` transaction using our `example-plugin` CLI,
|
||||
but we were already able to do that with the `basecoin` CLI.
|
||||
With our new CLI, however, we can also send an `ExamplePluginTx`:
|
||||
|
||||
```
|
||||
example-plugin tx example --amount 1mycoin
|
||||
```
|
||||
|
||||
The transaction is invalid! That's because we didn't specify the `--valid` flag:
|
||||
|
||||
```
|
||||
example-plugin tx example --valid --amount 1mycoin
|
||||
```
|
||||
|
||||
Tada! We successfuly created, signed, broadcast, and processed our custom transaction type.
|
||||
|
||||
## Query
|
||||
|
||||
Now that we've sent a transaction to update the state, let's query for the state.
|
||||
Recall that the state is stored under the key `ExamplePlugin.State`:
|
||||
|
||||
|
||||
```
|
||||
example-plugin query ExamplePlugin.State
|
||||
```
|
||||
|
||||
Note the `"value":"0101"` piece. This is the serialized form of the state,
|
||||
which contains only an integer.
|
||||
If we send another transaction, and then query again, we'll see the value increment:
|
||||
|
||||
```
|
||||
example-plugin tx example --valid --amount 1mycoin
|
||||
example-plugin query ExamplePlugin.State
|
||||
```
|
||||
|
||||
Neat, right? Notice how the result of the query comes with a proof.
|
||||
This is a Merkle proof that the state is what we say it is.
|
||||
In a latter [tutorial on InterBlockchain Communication](ibc.md),
|
||||
we'll put this proof to work!
|
||||
|
||||
## Next Steps
|
||||
|
||||
In this tutorial we demonstrated how to create a new plugin and how to extend the
|
||||
basecoin CLI to activate the plugin on the blockchain and to send transactions to it.
|
||||
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,
|
||||
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)
|
|
@ -1,14 +0,0 @@
|
|||
# Plugin Examples
|
||||
|
||||
Now that we've seen [how to write a simple plugin](/docs/guide/example-plugin.md)
|
||||
and taken a look at [how the plugin system is designed](/docs/guide/plugin-design.md),
|
||||
it's time for some more advanced examples.
|
||||
|
||||
For now, most examples are contained in the `github.com/tendermint/basecoin-examples` repository.
|
||||
In particular, we have the following:
|
||||
|
||||
1. [Mintcoin](https://github.com/tendermint/basecoin-examples/tree/develop/mintcoin) - a plugin for issuing new Basecoin tokens
|
||||
2. [Trader](https://github.com/tendermint/basecoin-examples/tree/develop/trader) - a plugin for adding escrow and options features to Basecoin
|
||||
3. [Stakecoin](https://github.com/tendermint/basecoin-examples/tree/develop/stake) - a plugin for bonding and unbonding Tendermint validators and updating the validator set accordingly
|
||||
4. [PayToVote](https://github.com/tendermint/basecoin-examples/tree/develop/paytovote) - a plugin for creating issues and voting on them
|
||||
5. [IBC](/docs/guide/ibc.md) - a plugin for facilitating InterBlockchain Communication
|
|
@ -1,79 +0,0 @@
|
|||
# Basecoin Plugins
|
||||
|
||||
Basecoin implements a simple cryptocurrency, which is useful in and of itself,
|
||||
but is far more useful if it can support additional functionality.
|
||||
Here we describe how that functionality can be achieved through a plugin system.
|
||||
|
||||
|
||||
## AppTx
|
||||
|
||||
In addition to the `SendTx`, Basecoin also defines another transaction type, the `AppTx`:
|
||||
|
||||
```golang
|
||||
type AppTx struct {
|
||||
Gas int64 `json:"gas"`
|
||||
Fee Coin `json:"fee"`
|
||||
Input TxInput `json:"input"`
|
||||
Name string `json:"type"` // Name of the plugin
|
||||
Data []byte `json:"data"` // Data for the plugin to process
|
||||
}
|
||||
```
|
||||
|
||||
The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins.
|
||||
The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction,
|
||||
and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing.
|
||||
|
||||
Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`.
|
||||
It also includes a single `TxInput`, which specifies the sender of the transaction,
|
||||
and some coins that can be forwarded to the plugin as well.
|
||||
|
||||
## Plugins
|
||||
|
||||
A plugin is simply a Go package that implements the `Plugin` interface:
|
||||
|
||||
```golang
|
||||
type Plugin interface {
|
||||
|
||||
// Name of this plugin, should be short.
|
||||
Name() string
|
||||
|
||||
// Run a transaction from ABCI DeliverTx
|
||||
RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result)
|
||||
|
||||
// Other ABCI message handlers
|
||||
SetOption(store KVStore, key string, value string) (log string)
|
||||
InitChain(store KVStore, vals []*abci.Validator)
|
||||
BeginBlock(store KVStore, hash []byte, header *abci.Header)
|
||||
EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock)
|
||||
}
|
||||
|
||||
type CallContext struct {
|
||||
CallerAddress []byte // Caller's Address (hash of PubKey)
|
||||
CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted
|
||||
Coins Coins // The coins that the caller wishes to spend, excluding fees
|
||||
}
|
||||
```
|
||||
|
||||
The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed.
|
||||
The `Data` from the `AppTx` is passed in as the `txBytes`,
|
||||
while the `Input` from the `AppTx` is used to populate the `CallContext`.
|
||||
|
||||
Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data.
|
||||
By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly,
|
||||
and also to store arbitrary other information in the state.
|
||||
In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended.
|
||||
One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin!
|
||||
|
||||
Any required plugin initialization should be constructed within `SetOption`.
|
||||
`SetOption` may be called during genesis of basecoin and can be used to set
|
||||
initial plugin parameters. Within genesis.json file entries are made in
|
||||
the format: `"<plugin>/<key>", "<value>"`, where `<plugin>` is the plugin name,
|
||||
and `<key>` and `<value>` are the strings passed into the plugin SetOption function.
|
||||
This function is intended to be used to set plugin specific information such
|
||||
as the plugin state.
|
||||
|
||||
## Examples
|
||||
|
||||
To get started with plugins, see [the example-plugin tutorial](example-plugin.md).
|
||||
For more examples, see [the advanced plugin tutorial](more-examples.md).
|
||||
If you're really brave, see the tutorial on [implementing Interblockchain Communication as a plugin](ibc.md).
|
Loading…
Reference in New Issue