update docs, move counter

int

int

int
This commit is contained in:
rigel rozanski 2017-06-18 19:01:54 -04:00
parent 7dad89b152
commit 24bd0f5ed6
15 changed files with 395 additions and 354 deletions

View File

@ -9,6 +9,7 @@ build:
install:
go install ./cmd/...
go install ./docs/guide/counter/cmd/...
dist:
@bash scripts/dist.sh

View File

@ -19,8 +19,5 @@ dependencies:
test:
override:
- "cd $REPO && glide install && go install ./cmd/..."
- "cd $REPO && make all"
- ls $GOPATH/bin
- "cd $REPO && make test"

View File

@ -79,7 +79,7 @@ func (a *AppTx) AddSigner(pk crypto.PubKey) {
// but that code is too ugly now, needs refactor..
func (a *AppTx) ValidateBasic() error {
if a.chainID == "" {
return errors.New("No chainId specified")
return errors.New("No chain-id specified")
}
in := a.Tx.Input
if len(in.Address) != 20 {

View File

@ -80,7 +80,7 @@ func (s *SendTx) AddSigner(pk crypto.PubKey) {
// but that code is too ugly now, needs refactor..
func (s *SendTx) ValidateBasic() error {
if s.chainID == "" {
return errors.New("No chainId specified")
return errors.New("No chain-id specified")
}
for _, in := range s.Tx.Inputs {
if len(in.Address) != 20 {

View File

@ -1,11 +0,0 @@
package main
import (
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/types"
)
func init() {
commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() })
}

View File

@ -1,79 +1,93 @@
# 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`.
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 example we explore a simple plugin name `counter`.
## 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:
The design of the `basecoin` tool makes it easy to extend for custom
functionality. The Counter plugin is bundled with basecoin, so if you have
already [installed basecoin](install.md) then you should be able to run a full
node with `counter` and the a light-client `countercli` from terminal. The
Counter plugin is just like the `basecoin` tool. They both use the same
library of commands, including one for signing and broadcasting `SendTx`.
Counter transactions take two custom inputs, a boolean argument named `valid`,
and a coin amount named `countfee`. The transaction is only accepted if both
`valid` is set to true and the transaction input coins is greater than
`countfee` that the user provides.
A new blockchain can be initialized and started just like with in the [previous
guide](basecoin-basics.md):
```
cd $GOPATH/src/github.com/tendermint/basecoin
go install ./docs/guide/src/example-plugin
```
counter init
countercli keys new cool
countercli keys new friend
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.
GENKEY=`countercli keys get cool -o json | jq .pubkey.data`
GENJSON=`cat ~/.counter/genesis.json`
echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY > ~/.counter/genesis.json
A new blockchain can be initialized and started just like with `basecoin`:
counter start
```
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:
The default files are stored in `~/.counter`. In another window we can
initialize the light-client and send a transaction:
```
example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 1mycoin
countercli init --chain-id=test_chain_id --node=tcp://localhost:46657
YOU=`countercli keys get friend -o=json | jq .address | tr -d '"'`
countercli tx send --name=cool --amount=1000mycoin --to=0x$YOU --sequence=1
```
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:
But the Counter has an additional command, `countercli tx counter`, which
crafts an `AppTx` specifically for this plugin:
```
example-plugin tx example --amount 1mycoin
example-plugin tx example --amount 1mycoin --valid
countercli tx counter --name cool --amount=1mycoin --sequence=2
countercli tx counter --name cool --amount=1mycoin --sequence=3 --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:
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
countercli query counter
```
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:
Tada! We can now see that our custom counter plugin tx went through. You
should see a Counter value of 1 representing 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
countercli tx counter --name cool --amount=1mycoin --sequence=4 --valid
countercli query counter
```
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!
The value Counter value should be 2, 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.
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.
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 {
@ -85,13 +99,15 @@ type AppTx struct {
}
```
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.
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.
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
@ -120,43 +136,59 @@ type CallContext struct {
}
```
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`.
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!
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).
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.
To implement your own plugin and tooling, make a copy of
`docs/guide/counter`, 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.
First is the `cmd/counter/main.go`, which drives the program. It can be left
alone, but you should change any occurrences of `counter` 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`.
The light-client which is located in `cmd/countercli/main.go` allows for is
where transaction and query commands are designated. Similarity this command
can be mostly left alone besides replacing the application name and adding
references to new plugin commands
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.
Next is the custom commands in `cmd/countercli/commands/`. These files is
where we extend the tool with any new commands and flags we need to send
transactions or queries 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`.
For more examples and inspiration, see our [repository of example plugins](https://github.com/tendermint/basecoin-examples).
Finally is `plugins/counter/counter.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 `CounterTx`,
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.
`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.

View File

@ -1,12 +1,14 @@
# The Basecoin Tool
In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guide/basecoin-basics.md)
and [how to implement a plugin](/docs/guide/basecoin-plugins.md).
In this tutorial, we provide more details on using the `basecoin` tool.
In previous tutorials we learned the [basics of the Basecoin
CLI](/docs/guide/basecoin-basics.md) and [how to implement a
plugin](/docs/guide/basecoin-plugins.md). In this tutorial, we provide more
details on using the Basecoin tool.
# Data Directory
By default, `basecoin` works out of `~/.basecoin`. To change this, set the `BCHOME` environment variable:
By default, `basecoin` works out of `~/.basecoin`. To change this, set the
`BCHOME` environment variable:
```
export BCHOME=~/.my_basecoin_data
@ -23,15 +25,16 @@ BCHOME=~/.my_basecoin_data basecoin start
# ABCI Server
So far we have run Basecoin and Tendermint in a single process.
However, since we use ABCI, we can actually run them in different processes.
First, initialize them:
So far we have run Basecoin and Tendermint in a single process. However, since
we use ABCI, we can actually run them in different processes. First,
initialize them:
```
basecoin init
```
This will create a single `genesis.json` file in `~/.basecoin` with the information for both Basecoin and Tendermint.
This will create a single `genesis.json` file in `~/.basecoin` with the
information for both Basecoin and Tendermint.
Now, In one window, run
@ -47,7 +50,8 @@ TMROOT=~/.basecoin tendermint node
You should see Tendermint start making blocks!
Alternatively, you could ignore the Tendermint details in `~/.basecoin/genesis.json` and use a separate directory by running:
Alternatively, you could ignore the Tendermint details in
`~/.basecoin/genesis.json` and use a separate directory by running:
```
tendermint init
@ -58,9 +62,11 @@ For more details on using `tendermint`, see [the guide](https://tendermint.com/d
# Keys and Genesis
In previous tutorials we used `basecoin init` to initialize `~/.basecoin` with the default configuration.
This command creates files both for Tendermint and for Basecoin, and a single `genesis.json` file for both of them.
For more information on these files, see the [guide to using tendermint](https://tendermint.com/docs/guides/using-tendermint).
In previous tutorials we used `basecoin init` to initialize `~/.basecoin` with
the default configuration. This command creates files both for Tendermint and
for Basecoin, and a single `genesis.json` file for both of them. For more
information on these files, see the [guide to using
Tendermint](https://tendermint.com/docs/guides/using-tendermint).
Now let's make our own custom Basecoin data.
@ -70,67 +76,88 @@ First, create a new directory:
mkdir example-data
```
We can tell `basecoin` to use this directory by exporting the `BCHOME` environment variable:
We can tell `basecoin` to use this directory by exporting the `BCHOME`
environment variable:
```
export BCHOME=$(pwd)/example-data
```
If you're going to be using multiple terminal windows, make sure to add this variable to your shell startup scripts (eg. `~/.bashrc`).
If you're going to be using multiple terminal windows, make sure to add this
variable to your shell startup scripts (eg. `~/.bashrc`).
Now, let's create a new private key:
Now, let's create a new key:
```
basecoin key new > $BCHOME/key.json
basecli keys new foobar
```
Here's what my `key.json looks like:
The key's info can be retrieved with
```
basecli keys get foobar -o=json
```
You should get output which looks similar to the following:
```json
{
"address": "4EGEhnqOw/gX326c7KARUkY1kic=",
"pub_key": {
"type": "ed25519",
"data": "a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141"
},
"priv_key": {
"type": "ed25519",
"data": "654c845f4b36d1a881deb0ff09381165d3ccd156b4aabb5b51267e91f1d024a5a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141"
}
}
```
Yours will look different - each key is randomly derrived.
Now we can make a `genesis.json` file and add an account with our public key:
```json
{
"chain_id": "example-chain",
"app_options": {
"accounts": [{
"pub_key": {
"type": "ed25519",
"data": "a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141"
},
"coins": [
{
"denom": "gold",
"amount": 1000000000
}
]
}]
"name": "foobar",
"address": "404C5003A703C7DA888C96A2E901FCE65A6869D9",
"pubkey": {
"type": "ed25519",
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
}
}
```
Here we've granted ourselves `1000000000` units of the `gold` token.
Note that we've also set the `chain-id` to be `example-chain`.
All transactions must therefore include the `--chain-id example-chain` in order to make sure they are valid for this chain.
Previously, we didn't need this flag because we were using the default chain ID ("test_chain_id").
Now that we're using a custom chain, we need to specify the chain explicitly on the command line.
Yours will look different - each key is randomly derived. Now we can make a
`genesis.json` file and add an account with our public key:
Note we have also left out the details of the tendermint genesis. These are documented in the [tendermint guide](https://tendermint.com/docs/guides/using-tendermint).
```json
{
"app_hash": "",
"chain_id": "example-chain",
"genesis_time": "0001-01-01T00:00:00.000Z",
"validators": [
{
"amount": 10,
"name": "",
"pub_key": {
"type": "ed25519",
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
}
}
],
"app_options": {
"accounts": [
{
"pub_key": {
"type": "ed25519",
"data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA"
},
"coins": [
{
"denom": "gold",
"amount": 1000000000
}
]
}
]
}
}
```
Here we've granted ourselves `1000000000` units of the `gold` token. Note that
we've also set the `chain-id` to be `example-chain`. All transactions must
therefore include the `--chain-id example-chain` in order to make sure they are
valid for this chain. Previously, we didn't need this flag because we were
using the default chain ID ("test_chain_id"). Now that we're using a custom
chain, we need to specify the chain explicitly on the command line.
Note we have also left out the details of the Tendermint genesis. These are
documented in the [Tendermint
guide](https://tendermint.com/docs/guides/using-tendermint).
# Reset
@ -141,13 +168,19 @@ You can reset all blockchain data by running:
basecoin unsafe_reset_all
```
Similarity you can reset client data by running:
```
basecli 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.
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.

View File

@ -5,8 +5,11 @@ import (
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
"github.com/tendermint/basecoin/types"
)
func main() {
@ -22,6 +25,7 @@ func main() {
commands.VersionCmd,
)
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin"))
commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() })
cmd := cli.PrepareMainCmd(RootCmd, "CT", os.ExpandEnv("$HOME/.counter"))
cmd.Execute()
}

View File

@ -8,7 +8,7 @@ import (
txcmd "github.com/tendermint/light-client/commands/txs"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
btypes "github.com/tendermint/basecoin/types"
)

View File

@ -5,7 +5,7 @@ import (
proofcmd "github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/docs/guide/counter/plugins/counter"
)
//CounterQueryCmd CLI command to query the counter state

View File

@ -14,12 +14,12 @@ import (
"github.com/tendermint/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
bcount "github.com/tendermint/basecoin/cmd/countercli/commands"
bcount "github.com/tendermint/basecoin/docs/guide/counter/cmd/countercli/commands"
)
// BaseCli represents the base command when called without any subcommands
var BaseCli = &cobra.Command{
Use: "basecli",
Use: "countercli",
Short: "Light client for tendermint",
Long: `Basecli is an version of tmcli including custom logic to
present a nice (not raw hex) interface to the basecoin blockchain structure.
@ -33,21 +33,25 @@ func main() {
commands.AddBasicFlags(BaseCli)
// Prepare queries
pr := proofs.RootCmd
// These are default parsers, but you optional in your app
pr.AddCommand(proofs.TxCmd)
pr.AddCommand(proofs.KeyCmd)
pr.AddCommand(bcmd.AccountQueryCmd)
proofs.RootCmd.AddCommand(
// These are default parsers, optional in your app
proofs.TxCmd,
proofs.KeyCmd,
bcmd.AccountQueryCmd,
// IMPORTANT: here is how you add custom query commands in your app
pr.AddCommand(bcount.CounterQueryCmd)
// XXX IMPORTANT: here is how you add custom query commands in your app
bcount.CounterQueryCmd,
)
// Prepare transactions
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
tr := txs.RootCmd
tr.AddCommand(bcmd.SendTxCmd)
txs.RootCmd.AddCommand(
// This is the default transaction, optional in your app
bcmd.SendTxCmd,
// IMPORTANT: here is how you add custom tx construction for your app
tr.AddCommand(bcount.CounterTxCmd)
// XXX IMPORTANT: here is how you add custom tx construction for your app
bcount.CounterTxCmd,
)
// Set up the various commands to use
BaseCli.AddCommand(
@ -55,10 +59,11 @@ func main() {
commands.ResetCmd,
keycmd.RootCmd,
seeds.RootCmd,
pr,
tr,
proxy.RootCmd)
proofs.RootCmd,
txs.RootCmd,
proxy.RootCmd,
)
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))
cmd := cli.PrepareMainCmd(BaseCli, "CTL", os.ExpandEnv("$HOME/.countercli"))
cmd.Execute()
}

View File

@ -1,29 +1,31 @@
# InterBlockchain Communication with Basecoin
One of the most exciting elements of the Cosmos Network is the InterBlockchain Communication (IBC) protocol,
which enables interoperability across different blockchains.
The simplest example of using the IBC protocol is to send a data packet from one blockchain to another.
One of the most exciting elements of the Cosmos Network is the InterBlockchain
Communication (IBC) protocol, which enables interoperability across different
blockchains. The simplest example of using the IBC protocol is to send a data
packet from one blockchain to another.
We implemented IBC as a basecoin plugin.
and here we'll show you how to use the Basecoin IBC-plugin to send a packet of data across blockchains!
We implemented IBC as a basecoin plugin. and here we'll show you how to use
the Basecoin IBC-plugin to send a packet of data across blockchains!
Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/basecoin-plugins.md),
but we'll explain how IBC works. You may also want to see [our repository of example plugins](https://github.com/tendermint/basecoin-examples).
Please note, this tutorial assumes you are familiar with [Basecoin
plugins](/docs/guide/basecoin-plugins.md), but we'll explain how IBC works. You
may also want to see [our repository of example
plugins](https://github.com/tendermint/basecoin-examples).
The IBC plugin defines a new set of transactions as subtypes of the `AppTx`.
The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`,
and setting the `Data` field to the serialized IBC transaction type.
The plugin's functionality is accessed by setting the `AppTx.Name` field to
`"IBC"`, and setting the `Data` field to the serialized IBC transaction type.
We'll demonstrate exactly how this works below.
## IBC
Let's review the IBC protocol.
The purpose of IBC is to enable one blockchain to function as a light-client of another.
Since we are using a classical Byzantine Fault Tolerant consensus algorithm,
light-client verification is cheap and easy:
all we have to do is check validator signatures on the latest block,
and verify a Merkle proof of the state.
Let's review the IBC protocol. The purpose of IBC is to enable one blockchain
to function as a light-client of another. Since we are using a classical
Byzantine Fault Tolerant consensus algorithm, light-client verification is
cheap and easy: all we have to do is check validator signatures on the latest
block, and verify a Merkle proof of the state.
In Tendermint, validators agree on a block before processing it. This means
that the signatures and state root for that block aren't included until the
@ -31,9 +33,9 @@ next block. Thus, each block contains a field called `LastCommit`, which
contains the votes responsible for committing the previous block, and a field
in the block header called `AppHash`, which refers to the Merkle root hash of
the application after processing the transactions from the previous block. So,
if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit`
at height H+1. (And remember that this `AppHash` only contains the results from all
transactions up to and including block H-1)
if we want to verify the `AppHash` from height H, we need the signatures from
`LastCommit` at height H+1. (And remember that this `AppHash` only contains the
results from all transactions up to and including block H-1)
Unlike Proof-of-Work, the light-client protocol does not need to download and
check all the headers in the blockchain - the client can always jump straight
@ -43,109 +45,94 @@ changes, which requires downloading headers for each block in which there is a
significant change. Here, we will assume the validator set is constant, and
postpone handling validator set changes for another time.
Now we can describe exactly how IBC works.
Suppose we have two blockchains, `chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`.
Now we can describe exactly how IBC works. Suppose we have two blockchains,
`chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`.
We need to do the following:
1. Register the details (ie. chain ID and genesis configuration) of `chain1` on `chain2`
2. Within `chain1`, broadcast a transaction that creates an outgoing IBC packet destined for `chain2`
3. Broadcast a transaction to `chain2` informing it of the latest state (ie. header and commit signatures) of `chain1`
4. Post the outgoing packet from `chain1` to `chain2`, including the proof that
it was indeed committed on `chain1`. Note `chain2` can only verify this proof
because it has a recent header and commit.
1. Register the details (ie. chain ID and genesis configuration) of `chain1`
on `chain2`
2. Within `chain1`, broadcast a transaction that creates an outgoing IBC
packet destined for `chain2`
3. Broadcast a transaction to `chain2` informing it of the latest state (ie.
header and commit signatures) of `chain1`
4. Post the outgoing packet from `chain1` to `chain2`, including the proof
that it was indeed committed on `chain1`. Note `chain2` can only verify
this proof because it has a recent header and commit.
Each of these steps involves a separate IBC transaction type. Let's take them up in turn.
Each of these steps involves a separate IBC transaction type. Let's take them
up in turn.
### IBCRegisterChainTx
The `IBCRegisterChainTx` is used to register one chain on another.
It contains the chain ID and genesis configuration of the chain to register:
The `IBCRegisterChainTx` is used to register one chain on another. It contains
the chain ID and genesis configuration of the chain to register:
```golang
type IBCRegisterChainTx struct {
BlockchainGenesis
}
```golang type IBCRegisterChainTx struct { BlockchainGenesis }
type BlockchainGenesis struct {
ChainID string
Genesis string
}
```
type BlockchainGenesis struct { ChainID string Genesis string } ```
This transaction should only be sent once for a given chain ID, and successive sends will return an error.
This transaction should only be sent once for a given chain ID, and successive
sends will return an error.
### IBCUpdateChainTx
The `IBCUpdateChainTx` is used to update the state of one chain on another.
It contains the header and commit signatures for some block in the chain:
The `IBCUpdateChainTx` is used to update the state of one chain on another. It
contains the header and commit signatures for some block in the chain:
```golang
type IBCUpdateChainTx struct {
Header tm.Header
Commit tm.Commit
}
```golang type IBCUpdateChainTx struct { Header tm.Header Commit tm.Commit }
```
In the future, it needs to be updated to include changes to the validator set as well.
Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequently as packets are being sent or the validator set is changing.
In the future, it needs to be updated to include changes to the validator set
as well. Anyone can relay an `IBCUpdateChainTx`, and they only need to do so
as frequently as packets are being sent or the validator set is changing.
### IBCPacketCreateTx
The `IBCPacketCreateTx` is used to create an outgoing packet on one chain.
The packet itself contains the source and destination chain IDs,
a sequence number (i.e. an integer that increments with every message sent between this pair of chains),
a packet type (e.g. coin, data, etc.),
and a payload.
The `IBCPacketCreateTx` is used to create an outgoing packet on one chain. The
packet itself contains the source and destination chain IDs, a sequence number
(i.e. an integer that increments with every message sent between this pair of
chains), a packet type (e.g. coin, data, etc.), and a payload.
```golang
type IBCPacketCreateTx struct {
Packet
}
```golang type IBCPacketCreateTx struct { Packet }
type Packet struct {
SrcChainID string
DstChainID string
Sequence uint64
Type string
Payload []byte
}
```
type Packet struct { SrcChainID string DstChainID string Sequence uint64 Type
string Payload []byte } ```
We have yet to define the format for the payload, so, for now, it's just arbitrary bytes.
We have yet to define the format for the payload, so, for now, it's just
arbitrary bytes.
One way to think about this is that `chain2` has an account on `chain1`.
With a `IBCPacketCreateTx` on `chain1`, we send funds to that account.
Then we can prove to `chain2` that there are funds locked up for it in it's
account on `chain1`.
Those funds can only be unlocked with corresponding IBC messages back from
`chain2` to `chain1` sending the locked funds to another account on
One way to think about this is that `chain2` has an account on `chain1`. With
a `IBCPacketCreateTx` on `chain1`, we send funds to that account. Then we can
prove to `chain2` that there are funds locked up for it in it's account on
`chain1`. Those funds can only be unlocked with corresponding IBC messages
back from `chain2` to `chain1` sending the locked funds to another account on
`chain1`.
### IBCPacketPostTx
The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another.
It contains the packet and a proof that the packet was committed into the state of the sending chain:
The `IBCPacketPostTx` is used to post an outgoing packet from one chain to
another. It contains the packet and a proof that the packet was committed into
the state of the sending chain:
```golang
type IBCPacketPostTx struct {
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof
Packet
Proof *merkle.IAVLProof
}
```
```golang type IBCPacketPostTx struct { FromChainID string // The immediate
source of the packet, not always Packet.SrcChainID FromChainHeight uint64 //
The block height in which Packet was committed, to check Proof Packet Proof
*merkle.IAVLProof } ```
The proof is a Merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree.
It contains a list of nodes in the tree, which can be hashed together to get the Merkle root hash.
This hash must match the `AppHash` contained in the header at `FromChainHeight + 1`
- note the `+ 1` is necessary since `FromChainHeight` is the height in which the packet was committed,
and the resulting state root is not included until the next block.
The proof is a Merkle proof in an IAVL tree, our implementation of a balanced,
Merklized binary search tree. It contains a list of nodes in the tree, which
can be hashed together to get the Merkle root hash. This hash must match the
`AppHash` contained in the header at `FromChainHeight + 1`
- note the `+ 1` is necessary since `FromChainHeight` is the height in which
the packet was committed, and the resulting state root is not included until
the next block.
### IBC State
Now that we've seen all the transaction types, let's talk about the state.
Each chain stores some IBC state in its Merkle tree.
For each chain being tracked by our chain, we store:
Each chain stores some IBC state in its Merkle tree. For each chain being
tracked by our chain, we store:
- Genesis configuration
- Latest state
@ -154,143 +141,131 @@ For each chain being tracked by our chain, we store:
We also store all incoming (ingress) and outgoing (egress) packets.
The state of a chain is updated every time an `IBCUpdateChainTx` is committed.
New packets are added to the egress state upon `IBCPacketCreateTx`.
New packets are added to the ingress state upon `IBCPacketPostTx`,
assuming the proof checks out.
New packets are added to the egress state upon `IBCPacketCreateTx`. New
packets are added to the ingress state upon `IBCPacketPostTx`, assuming the
proof checks out.
## Merkle Queries
The Basecoin application uses a single Merkle tree that is shared across all its state,
including the built-in accounts state and all plugin state. For this reason,
it's important to use explicit key names and/or hashes to ensure there are no collisions.
The Basecoin application uses a single Merkle tree that is shared across all
its state, including the built-in accounts state and all plugin state. For this
reason, it's important to use explicit key names and/or hashes to ensure there
are no collisions.
We can query the Merkle tree using the ABCI Query method.
If we pass in the correct key, it will return the corresponding value,
as well as a proof that the key and value are contained in the Merkle tree.
We can query the Merkle tree using the ABCI Query method. If we pass in the
correct key, it will return the corresponding value, as well as a proof that
the key and value are contained in the Merkle tree.
The results of a query can thus be used as proof in an `IBCPacketPostTx`.
## Try it out
Now that we have all the background knowledge, let's actually walk through the tutorial.
Now that we have all the background knowledge, let's actually walk through the
tutorial.
Make sure you have installed
[Tendermint](https://tendermint.com/intro/getting-started/download) and
[basecoin](/docs/guide/install.md).
`basecoin` is a framework for creating new cryptocurrency applications.
It comes with an `IBC` plugin enabled by default.
`basecoin` is a framework for creating new cryptocurrency applications. It
comes with an `IBC` plugin enabled by default.
You will also want to install the [jq](https://stedolan.github.io/jq/) for handling JSON at the command line.
You will also want to install the [jq](https://stedolan.github.io/jq/) for
handling JSON at the command line.
Now let's start the two blockchains.
In this tutorial, each chain will have only a single validator,
where the initial configuration files are already generated.
Let's change directory so these files are easily accessible:
Now let's start the two blockchains. In this tutorial, each chain will have
only a single validator, where the initial configuration files are already
generated. Let's change directory so these files are easily accessible:
```
cd $GOPATH/src/github.com/tendermint/basecoin/demo
```
``` cd $GOPATH/src/github.com/tendermint/basecoin/demo ```
The relevant data is now in the `data` directory.
Before we begin, let's set some environment variables for convenience:
The relevant data is now in the `data` directory. Before we begin, let's set
some environment variables for convenience:
```
export BCHOME="."
BCHOME1="./data/chain1"
BCHOME2="./data/chain2"
``` export BCHOME="." BCHOME1="./data/chain1" BCHOME2="./data/chain2"
export CHAIN_ID1=test_chain_1
export CHAIN_ID2=test_chain_2
export CHAIN_ID1=test_chain_1 export CHAIN_ID2=test_chain_2
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json"
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657"
```
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node
tcp://localhost:36657" ```
In previous examples, we started basecoin in-process with tendermint.
Here, we will run them in different processes, using the `--without-tendermint` flag,
as described in the [guide to the basecoin tool](basecoin-tool.md).
We can start the two chains as follows:
In previous examples, we started basecoin in-process with tendermint. Here, we
will run them in different processes, using the `--without-tendermint` flag, as
described in the [guide to the basecoin tool](basecoin-tool.md). We can start
the two chains as follows:
```
TMROOT=$BCHOME1 tendermint node --log_level=info &> chain1_tendermint.log &
``` TMROOT=$BCHOME1 tendermint node --log_level=info &> chain1_tendermint.log &
BCHOME=$BCHOME1 basecoin start --without-tendermint &> chain1_basecoin.log &
```
and
```
TMROOT=$BCHOME2 tendermint node --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log &
BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> chain2_basecoin.log &
``` TMROOT=$BCHOME2 tendermint node --log_level=info --node_laddr
tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app
tcp://localhost:36658 &> chain2_tendermint.log & BCHOME=$BCHOME2 basecoin start
--address tcp://localhost:36658 --without-tendermint &> chain2_basecoin.log &
```
Note how we refer to the relevant data directories, and how we set the various addresses for the second node so as not to conflict with the first.
Note how we refer to the relevant data directories, and how we set the various
addresses for the second node so as not to conflict with the first.
We can now check on the status of the two chains:
```
curl localhost:46657/status
curl localhost:36657/status
```
``` curl localhost:46657/status curl localhost:36657/status ```
If either command fails, the nodes may not have finished starting up. Wait a couple seconds and try again.
Once you see the status of both chains, it's time to move on.
If either command fails, the nodes may not have finished starting up. Wait a
couple seconds and try again. Once you see the status of both chains, it's
time to move on.
In this tutorial, we're going to send some data from `test_chain_1` to `test_chain_2`.
We begin by registering `test_chain_1` on `test_chain_2`:
In this tutorial, we're going to send some data from `test_chain_1` to
`test_chain_2`. We begin by registering `test_chain_1` on `test_chain_2`:
```
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json
```
``` basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id
$CHAIN_ID1 --genesis $BCHOME1/genesis.json ```
Now we can create the outgoing packet on `test_chain_1`:
```
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --ibc_sequence 1
``` basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from
$CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --ibc_sequence 1
```
Note our payload is just `DEADBEEF`.
Now that the packet is committed in the chain, let's get some proof by querying:
Note our payload is just `DEADBEEF`. Now that the packet is committed in the
chain, let's get some proof by querying:
```
QUERY=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1)
echo $QUERY
```
``` QUERY=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) echo $QUERY ```
The result contains the latest height, a value (i.e. the hex-encoded binary serialization of our packet),
and a proof (i.e. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree.
We keep the result in the `QUERY` variable so we can easily reference subfields using the `jq` tool.
The result contains the latest height, a value (i.e. the hex-encoded binary
serialization of our packet), and a proof (i.e. hex-encoded binary
serialization of a list of nodes from the Merkle tree) that the value is in the
Merkle tree. We keep the result in the `QUERY` variable so we can easily
reference subfields using the `jq` tool.
If we want to send this data to `test_chain_2`, we first have to update what it knows about `test_chain_1`.
We'll need a recent block header and a set of commit signatures.
Fortunately, we can get them with the `block` command:
If we want to send this data to `test_chain_2`, we first have to update what it
knows about `test_chain_1`. We'll need a recent block header and a set of
commit signatures. Fortunately, we can get them with the `block` command:
```
BLOCK=$(basecoin block $(echo $QUERY | jq .height))
echo $BLOCK
```
``` BLOCK=$(basecoin block $(echo $QUERY | jq .height)) echo $BLOCK ```
Here, we are passing `basecoin block` the `height` from our earlier query.
Note the result contains both a hex-encoded and json-encoded version of the header and the commit.
The former is used as input for later commands; the latter is human-readable, so you know what's going on!
Note the result contains both a hex-encoded and json-encoded version of the
header and the commit. The former is used as input for later commands; the
latter is human-readable, so you know what's going on!
Let's send this updated information about `test_chain_1` to `test_chain_2`.
First, output the header and commit for reference:
```
echo $BLOCK | jq .hex.header
echo $BLOCK | jq .hex.commit
```
``` echo $BLOCK | jq .hex.header echo $BLOCK | jq .hex.commit ```
And now forward those values to `test_chain_2`:
```
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x<header> --commit 0x<commit>
```
``` basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x<header>
--commit 0x<commit> ```
Now that `test_chain_2` knows about some recent state of `test_chain_1`, we can post the packet to `test_chain_2`,
along with proof the packet was committed on `test_chain_1`. Since `test_chain_2` knows about some recent state
of `test_chain_1`, it will be able to verify the proof!
Now that `test_chain_2` knows about some recent state of `test_chain_1`, we can
post the packet to `test_chain_2`, along with proof the packet was committed on
`test_chain_1`. Since `test_chain_2` knows about some recent state of
`test_chain_1`, it will be able to verify the proof!
First, output the height, packet, and proof for reference:
@ -303,18 +278,22 @@ echo $QUERY | jq .proof
And forward those values to `test_chain_2`:
```
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height <height> --packet 0x<packet> --proof 0x<proof>
basecoin tx ibc --amount=10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height <height> --packet 0x<packet> --proof 0x<proof>
```
If the command does not return an error, then we have successfuly transfered data from `test_chain_1` to `test_chain_2`. Tada!
If the command does not return an error, then we have successfuly transfered
data from `test_chain_1` to `test_chain_2`. Tada!
## Conclusion
In this tutorial we explained how IBC works, and demonstrated how to use it to communicate between two chains.
We did the simplest communciation possible: a one way transfer of data from chain1 to chain2.
The most important part was that we updated chain2 with the latest state (i.e. header and commit) of chain1,
and then were able to post a proof to chain2 that a packet was committed to the outgoing state of chain1.
In this tutorial we explained how IBC works, and demonstrated how to use it to
communicate between two chains. We did the simplest communciation possible: a
one way transfer of data from chain1 to chain2. The most important part was
that we updated chain2 with the latest state (i.e. header and commit) of
chain1, and then were able to post a proof to chain2 that a packet was
committed to the outgoing state of chain1.
In a future tutorial, we will demonstrate how to use IBC to actually transfer tokens between two blockchains,
but we'll do it with real testnets deployed across multiple nodes on the network. Stay tuned!
In a future tutorial, we will demonstrate how to use IBC to actually transfer
tokens between two blockchains, but we'll do it with real testnets deployed
across multiple nodes on the network. Stay tuned!

View File

@ -3,7 +3,8 @@
On a good day, basecoin can be installed like a normal Go program:
```
go get -u github.com/tendermint/basecoin/cmd/basecoin
go get github.com/tendermint/basecoin/
make all
```
In some cases, if that fails, or if another branch is required,
@ -15,7 +16,7 @@ the correct way to install is:
cd $GOPATH/src/github.com/tendermint/basecoin
git pull origin master
make get_vendor_deps
make install
make all
```
This will create the `basecoin` binary in `$GOPATH/bin`.