commit
a50146d850
93
Plugins.md
93
Plugins.md
|
@ -1,93 +0,0 @@
|
||||||
# Basecoin Plugins
|
|
||||||
|
|
||||||
Basecoin is an extensible cryptocurrency module.
|
|
||||||
Each Basecoin account contains a ED25519 public key,
|
|
||||||
a balance in many different coin denominations,
|
|
||||||
and a strictly increasing sequence number for replay protection (like in Ethereum).
|
|
||||||
Accounts are serialized and stored in a merkle tree using the account's address as the key,
|
|
||||||
where the address is the RIPEMD160 hash of the public key.
|
|
||||||
|
|
||||||
Sending tokens around is done via the `SendTx`, which 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:
|
|
||||||
|
|
||||||
```
|
|
||||||
type SendTx struct {
|
|
||||||
Gas int64 `json:"gas"` // Gas
|
|
||||||
Fee Coin `json:"fee"` // 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"` //
|
|
||||||
}
|
|
||||||
|
|
||||||
type Coins []Coin
|
|
||||||
|
|
||||||
type Coin struct {
|
|
||||||
Denom string `json:"denom"`
|
|
||||||
Amount int64 `json:"amount"`
|
|
||||||
}
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Note it also 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.
|
|
||||||
|
|
||||||
|
|
||||||
Basecoin also defines another transaction type, the `AppTx`:
|
|
||||||
|
|
||||||
```
|
|
||||||
type AppTx struct {
|
|
||||||
Gas int64 `json:"gas"` // Gas
|
|
||||||
Fee Coin `json:"fee"` // Fee
|
|
||||||
Name string `json:"type"` // Which plugin
|
|
||||||
Input TxInput `json:"input"`
|
|
||||||
Data []byte `json:"data"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `AppTx` enables arbitrary additional functionality through the use of plugins.
|
|
||||||
A plugin is simply a Go package that implements the `Plugin` interface:
|
|
||||||
|
|
||||||
```
|
|
||||||
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, height uint64)
|
|
||||||
EndBlock(store KVStore, height uint64) []*abci.Validator
|
|
||||||
}
|
|
||||||
|
|
||||||
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 `Name` field in the `AppTx` refers to the plugin name, and the `Data` field of the `AppTx` is
|
|
||||||
forward to the `RunTx` function.
|
|
||||||
|
|
||||||
You can look at some example plugins in the [basecoin repo](https://github.com/tendermint/basecoin/tree/develop/plugins).
|
|
||||||
|
|
||||||
If you want to see how you can write a plugin in your own repo, and make use of all the basecoin tooling, cli, etc. please take a look at the [mintcoin example](https://github.com/tendermint/basecoin-examples/tree/master/mintcoin) for inspiration, not just the plugin itself, but also the `cmd/mintcoin` directory to create the custom command.
|
|
70
README.md
70
README.md
|
@ -2,20 +2,16 @@
|
||||||
|
|
||||||
DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service.
|
DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service.
|
||||||
|
|
||||||
Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. This project has two main purposes:
|
Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency.
|
||||||
|
It also provides a general purpose framework for extending the feature-set of the cryptocurrency
|
||||||
|
by implementing plugins.
|
||||||
|
|
||||||
1. As an example for anyone wishing to build a custom application using tendermint.
|
Basecoin serves as a reference implementation for how we build ABCI applications in Go,
|
||||||
2. As a framework for anyone wishing to build a tendermint-based currency, extensible using the plugin system.
|
and is the framework in which we implement the [Cosmos Hub](https://cosmos.network).
|
||||||
|
It's easy to use, and doesn't require any forking - just implement your plugin, import the basecoin libraries,
|
||||||
|
and away you go with a full-stack blockchain and command line tool for transacting.
|
||||||
|
|
||||||
If you wish to use basecoin as a framework to build your application, you most likely do not need to fork basecoin or modify it in any way. In fact, even the cli tool is designed to be easily extended by third party repos with almost no copying of code. You just need to add basecoin as a dependency in the `vendor` dir and take a look at [some examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) of how to customize it without modifying the code.
|
WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready.
|
||||||
|
|
||||||
## Contents
|
|
||||||
|
|
||||||
1. [Installation](#installation)
|
|
||||||
1. [Using the plugin system](#using-the-plugin-system)
|
|
||||||
1. [Using the cli](#using-the-cli)
|
|
||||||
1. [Tutorials and other reading](#tutorials-and-other-reading)
|
|
||||||
1. [Contributing](#contributing)
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -30,49 +26,21 @@ make install
|
||||||
|
|
||||||
This will create the `basecoin` binary in `$GOPATH/bin`.
|
This will create the `basecoin` binary in `$GOPATH/bin`.
|
||||||
|
|
||||||
## Using the Plugin System
|
## Command Line Interface
|
||||||
|
|
||||||
Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications.
|
The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`),
|
||||||
It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...),
|
|
||||||
sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state.
|
|
||||||
These are common factors that many people wish to have in a crypto-currency system,
|
|
||||||
so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system, just writing the custom business logic they need, and leaving the rest to the basecoin system.
|
|
||||||
|
|
||||||
Interested in building a plugin? Then [read more details here](./Plugins.md) and then you can follow a [simple tutorial](https://github.com/tendermint/basecoin-examples/blob/master/pluginDev/tutorial.md) to get your first plugin working.
|
|
||||||
|
|
||||||
### Best Practices
|
|
||||||
|
|
||||||
We are still trying out sort out the best practices for basecoin plugins, and ABCi apps in general. Flexibility is very powerful once one has mastered a system, but when starting out, it is nice to have a set of guidelines to follow (and then expand beyond when no longer needed). I have attempted to gather some [good design practices](https://github.com/tendermint/basecoin-examples/tree/master/trader#code-design) I have discovered/invented while building progress. These are not hard rules, but should give you a good start. And please give feedback to improve and extend them.
|
|
||||||
|
|
||||||
## Using the CLI
|
|
||||||
|
|
||||||
The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`),
|
|
||||||
or to start basecoin with tendermint in the same process (`basecoin start --in-proc`).
|
or to start basecoin with tendermint in the same process (`basecoin start --in-proc`).
|
||||||
It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100`
|
It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100`
|
||||||
See `basecoin --help` and `basecoin [cmd] --help` for more details`.
|
See `basecoin --help` and `basecoin [cmd] --help` for more details`.
|
||||||
|
|
||||||
Or follow through a [step-by-step introduction](https://github.com/tendermint/basecoin-examples/blob/master/tutorial.md) to testing basecoin locally.
|
## Learn more
|
||||||
|
|
||||||
## Tutorials and Other Reading
|
1. Getting started with the [Basecoin tool](/docs/guide/basecoin-basics.md)
|
||||||
|
1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.md)
|
||||||
|
1. Extend Basecoin [using the plugin system](/docs/guide/example-plugin.md)
|
||||||
|
1. Learn more about [plugin design](/docs/guide/plugin-design.md)
|
||||||
|
1. See some [more example applications](/docs/guide/more-examples.md)
|
||||||
|
1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md)
|
||||||
|
1. [Deploy testnets](deployment.md) running your basecoin application.
|
||||||
|
|
||||||
See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin.
|
|
||||||
|
|
||||||
There are a [number of examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) along with some tutorials and introductory texts, that should give you some pointers on how to wirte you own plugins and integrate them into your own custom app.
|
|
||||||
|
|
||||||
We are working on extending these examples, as well as documenting (and automating) setting up a testnet, and providing an example GUI for viewing basecoin, which can all be used as a starting point for your application. They should be published during the course of February 2017, so stay tuned....
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
We will merge in interesting plugin implementations and improvements to Basecoin.
|
|
||||||
|
|
||||||
If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start calling github.com/ORIGINAL/repo, which is very confusing. My preferred solution to this is as follows:
|
|
||||||
|
|
||||||
* Create your own fork on github, using the fork button.
|
|
||||||
* Go to the original repo checked out locally (from `go get`)
|
|
||||||
* `git remote rename origin upstream`
|
|
||||||
* `git remote add origin git@github.com:YOUR-NAME/basecoin.git`
|
|
||||||
* `git push -u origin master`
|
|
||||||
* You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork.
|
|
||||||
* If you want to pull in updates from the original repo:
|
|
||||||
* `git fetch upstream`
|
|
||||||
* `git rebase upstream/master` (or whatever branch you want)
|
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands.RegisterIBC()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "adam"
|
||||||
|
app.Usage = "adam [command] [args...]"
|
||||||
|
app.Version = "0.1.0"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
commands.StartCmd,
|
||||||
|
commands.TxCmd,
|
||||||
|
commands.KeyCmd,
|
||||||
|
commands.QueryCmd,
|
||||||
|
commands.VerifyCmd, // TODO: move to merkleeyes?
|
||||||
|
commands.BlockCmd,
|
||||||
|
commands.AccountCmd,
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/cmd/basecoin/commands"
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,12 +14,11 @@ func main() {
|
||||||
app.Version = "0.1.0"
|
app.Version = "0.1.0"
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
commands.StartCmd,
|
commands.StartCmd,
|
||||||
commands.SendTxCmd,
|
commands.TxCmd,
|
||||||
commands.AppTxCmd,
|
|
||||||
commands.IbcCmd,
|
|
||||||
commands.QueryCmd,
|
commands.QueryCmd,
|
||||||
commands.VerifyCmd,
|
commands.KeyCmd,
|
||||||
commands.BlockCmd,
|
commands.VerifyCmd, // TODO: move to merkleeyes?
|
||||||
|
commands.BlockCmd, // TODO: move to adam?
|
||||||
commands.AccountCmd,
|
commands.AccountCmd,
|
||||||
}
|
}
|
||||||
app.Run(os.Args)
|
app.Run(os.Args)
|
||||||
|
|
|
@ -31,11 +31,6 @@ var (
|
||||||
Name: "in-proc",
|
Name: "in-proc",
|
||||||
Usage: "Run Tendermint in-process with the App",
|
Usage: "Run Tendermint in-process with the App",
|
||||||
}
|
}
|
||||||
|
|
||||||
IbcPluginFlag = cli.BoolFlag{
|
|
||||||
Name: "ibc-plugin",
|
|
||||||
Usage: "Enable the ibc plugin",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// tx flags
|
// tx flags
|
||||||
|
@ -61,7 +56,7 @@ var (
|
||||||
|
|
||||||
FromFlag = cli.StringFlag{
|
FromFlag = cli.StringFlag{
|
||||||
Name: "from",
|
Name: "from",
|
||||||
Value: "priv_validator.json",
|
Value: "key.json",
|
||||||
Usage: "Path to a private key to sign the transaction",
|
Usage: "Path to a private key to sign the transaction",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,86 +101,6 @@ var (
|
||||||
Value: "test_chain_id",
|
Value: "test_chain_id",
|
||||||
Usage: "ID of the chain for replay protection",
|
Usage: "ID of the chain for replay protection",
|
||||||
}
|
}
|
||||||
|
|
||||||
ValidFlag = cli.BoolFlag{
|
|
||||||
Name: "valid",
|
|
||||||
Usage: "Set valid field in CounterTx",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ibc flags
|
|
||||||
var (
|
|
||||||
IbcChainIDFlag = cli.StringFlag{
|
|
||||||
Name: "chain_id",
|
|
||||||
Usage: "ChainID for the new blockchain",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcGenesisFlag = cli.StringFlag{
|
|
||||||
Name: "genesis",
|
|
||||||
Usage: "Genesis file for the new blockchain",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcHeaderFlag = cli.StringFlag{
|
|
||||||
Name: "header",
|
|
||||||
Usage: "Block header for an ibc update",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcCommitFlag = cli.StringFlag{
|
|
||||||
Name: "commit",
|
|
||||||
Usage: "Block commit for an ibc update",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcFromFlag = cli.StringFlag{
|
|
||||||
Name: "from",
|
|
||||||
Usage: "Source ChainID",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcToFlag = cli.StringFlag{
|
|
||||||
Name: "to",
|
|
||||||
Usage: "Destination ChainID",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcTypeFlag = cli.StringFlag{
|
|
||||||
Name: "type",
|
|
||||||
Usage: "IBC packet type (eg. coin)",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcPayloadFlag = cli.StringFlag{
|
|
||||||
Name: "payload",
|
|
||||||
Usage: "IBC packet payload",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcPacketFlag = cli.StringFlag{
|
|
||||||
Name: "packet",
|
|
||||||
Usage: "hex-encoded IBC packet",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcProofFlag = cli.StringFlag{
|
|
||||||
Name: "proof",
|
|
||||||
Usage: "hex-encoded proof of IBC packet from source chain",
|
|
||||||
Value: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcSequenceFlag = cli.IntFlag{
|
|
||||||
Name: "sequence",
|
|
||||||
Usage: "sequence number for IBC packet",
|
|
||||||
Value: 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
IbcHeightFlag = cli.IntFlag{
|
|
||||||
Name: "height",
|
|
||||||
Usage: "Height the packet became egress in source chain",
|
|
||||||
Value: 0,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// proof flags
|
// proof flags
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/plugins/ibc"
|
"github.com/tendermint/basecoin/plugins/ibc"
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
|
||||||
cmn "github.com/tendermint/go-common"
|
cmn "github.com/tendermint/go-common"
|
||||||
"github.com/tendermint/go-merkle"
|
"github.com/tendermint/go-merkle"
|
||||||
|
@ -16,25 +17,97 @@ import (
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Register the IBC plugin at start and for transactions
|
||||||
|
func RegisterIBC() {
|
||||||
|
RegisterTxSubcommand(IbcCmd)
|
||||||
|
RegisterStartPlugin("ibc", func() types.Plugin { return ibc.New() })
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// ibc flags
|
||||||
|
|
||||||
|
var (
|
||||||
|
IbcChainIDFlag = cli.StringFlag{
|
||||||
|
Name: "chain_id",
|
||||||
|
Usage: "ChainID for the new blockchain",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcGenesisFlag = cli.StringFlag{
|
||||||
|
Name: "genesis",
|
||||||
|
Usage: "Genesis file for the new blockchain",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcHeaderFlag = cli.StringFlag{
|
||||||
|
Name: "header",
|
||||||
|
Usage: "Block header for an ibc update",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcCommitFlag = cli.StringFlag{
|
||||||
|
Name: "commit",
|
||||||
|
Usage: "Block commit for an ibc update",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcFromFlag = cli.StringFlag{
|
||||||
|
Name: "from",
|
||||||
|
Usage: "Source ChainID",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcToFlag = cli.StringFlag{
|
||||||
|
Name: "to",
|
||||||
|
Usage: "Destination ChainID",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcTypeFlag = cli.StringFlag{
|
||||||
|
Name: "type",
|
||||||
|
Usage: "IBC packet type (eg. coin)",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcPayloadFlag = cli.StringFlag{
|
||||||
|
Name: "payload",
|
||||||
|
Usage: "IBC packet payload",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcPacketFlag = cli.StringFlag{
|
||||||
|
Name: "packet",
|
||||||
|
Usage: "hex-encoded IBC packet",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcProofFlag = cli.StringFlag{
|
||||||
|
Name: "proof",
|
||||||
|
Usage: "hex-encoded proof of IBC packet from source chain",
|
||||||
|
Value: "",
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcSequenceFlag = cli.IntFlag{
|
||||||
|
Name: "sequence",
|
||||||
|
Usage: "sequence number for IBC packet",
|
||||||
|
Value: 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
IbcHeightFlag = cli.IntFlag{
|
||||||
|
Name: "height",
|
||||||
|
Usage: "Height the packet became egress in source chain",
|
||||||
|
Value: 0,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// ibc commands
|
||||||
|
|
||||||
var (
|
var (
|
||||||
IbcCmd = cli.Command{
|
IbcCmd = cli.Command{
|
||||||
Name: "ibc",
|
Name: "ibc",
|
||||||
Usage: "Send a transaction to the interblockchain (ibc) plugin",
|
Usage: "Send a transaction to the interblockchain (ibc) plugin",
|
||||||
Flags: []cli.Flag{
|
Flags: TxFlags,
|
||||||
NodeFlag,
|
|
||||||
ChainIDFlag,
|
|
||||||
|
|
||||||
FromFlag,
|
|
||||||
|
|
||||||
AmountFlag,
|
|
||||||
CoinFlag,
|
|
||||||
GasFlag,
|
|
||||||
FeeFlag,
|
|
||||||
SeqFlag,
|
|
||||||
|
|
||||||
NameFlag,
|
|
||||||
DataFlag,
|
|
||||||
},
|
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
IbcRegisterTxCmd,
|
IbcRegisterTxCmd,
|
||||||
IbcUpdateTxCmd,
|
IbcUpdateTxCmd,
|
||||||
|
@ -69,9 +142,6 @@ var (
|
||||||
IbcPacketTxCmd = cli.Command{
|
IbcPacketTxCmd = cli.Command{
|
||||||
Name: "packet",
|
Name: "packet",
|
||||||
Usage: "Send a new packet via IBC",
|
Usage: "Send a new packet via IBC",
|
||||||
Flags: []cli.Flag{
|
|
||||||
//
|
|
||||||
},
|
|
||||||
Subcommands: []cli.Command{
|
Subcommands: []cli.Command{
|
||||||
IbcPacketCreateTx,
|
IbcPacketCreateTx,
|
||||||
IbcPacketPostTx,
|
IbcPacketPostTx,
|
||||||
|
@ -108,6 +178,9 @@ var (
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//---------------------------------------------------------------------
|
||||||
|
// ibc command implementations
|
||||||
|
|
||||||
func cmdIBCRegisterTx(c *cli.Context) error {
|
func cmdIBCRegisterTx(c *cli.Context) error {
|
||||||
chainID := c.String("chain_id")
|
chainID := c.String("chain_id")
|
||||||
genesisFile := c.String("genesis")
|
genesisFile := c.String("genesis")
|
|
@ -0,0 +1,73 @@
|
||||||
|
package commands
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
cmn "github.com/tendermint/go-common"
|
||||||
|
"github.com/tendermint/go-crypto"
|
||||||
|
"github.com/tendermint/go-wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
KeyCmd = cli.Command{
|
||||||
|
Name: "key",
|
||||||
|
Usage: "Manage keys",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Subcommands: []cli.Command{NewKeyCmd},
|
||||||
|
}
|
||||||
|
|
||||||
|
NewKeyCmd = cli.Command{
|
||||||
|
Name: "new",
|
||||||
|
Usage: "Create a new private key",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return cmdNewKey(c)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdNewKey(c *cli.Context) error {
|
||||||
|
key := genKey()
|
||||||
|
keyJSON := wire.JSONBytesPretty(key)
|
||||||
|
fmt.Println(string(keyJSON))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//---------------------------------------------
|
||||||
|
// simple implementation of a key
|
||||||
|
|
||||||
|
type Key struct {
|
||||||
|
Address []byte `json:"address"`
|
||||||
|
PubKey crypto.PubKey `json:"pub_key"`
|
||||||
|
PrivKey crypto.PrivKey `json:"priv_key"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements Signer
|
||||||
|
func (k *Key) Sign(msg []byte) crypto.Signature {
|
||||||
|
return k.PrivKey.Sign(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generates a new validator with private key.
|
||||||
|
func genKey() *Key {
|
||||||
|
privKey := crypto.GenPrivKeyEd25519()
|
||||||
|
return &Key{
|
||||||
|
Address: privKey.PubKey().Address(),
|
||||||
|
PubKey: privKey.PubKey(),
|
||||||
|
PrivKey: privKey,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadKey(filePath string) *Key {
|
||||||
|
keyJSONBytes, err := ioutil.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
cmn.Exit(err.Error())
|
||||||
|
}
|
||||||
|
key := wire.ReadJSON(&Key{}, keyJSONBytes, &err).(*Key)
|
||||||
|
if err != nil {
|
||||||
|
cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err))
|
||||||
|
}
|
||||||
|
return key
|
||||||
|
}
|
|
@ -19,7 +19,6 @@ import (
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/app"
|
"github.com/tendermint/basecoin/app"
|
||||||
"github.com/tendermint/basecoin/plugins/ibc"
|
|
||||||
"github.com/tendermint/basecoin/types"
|
"github.com/tendermint/basecoin/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -40,21 +39,19 @@ var StartCmd = cli.Command{
|
||||||
DirFlag,
|
DirFlag,
|
||||||
InProcTMFlag,
|
InProcTMFlag,
|
||||||
ChainIDFlag,
|
ChainIDFlag,
|
||||||
IbcPluginFlag,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type plugin struct {
|
type plugin struct {
|
||||||
name string
|
name string
|
||||||
init func() types.Plugin
|
newPlugin func() types.Plugin
|
||||||
}
|
}
|
||||||
|
|
||||||
var plugins = []plugin{}
|
var plugins = []plugin{}
|
||||||
|
|
||||||
// RegisterStartPlugin is used to add another
|
// RegisterStartPlugin is used to enable a plugin
|
||||||
func RegisterStartPlugin(flag cli.BoolFlag, init func() types.Plugin) {
|
func RegisterStartPlugin(name string, newPlugin func() types.Plugin) {
|
||||||
StartCmd.Flags = append(StartCmd.Flags, flag)
|
plugins = append(plugins, plugin{name: name, newPlugin: newPlugin})
|
||||||
plugins = append(plugins, plugin{name: flag.GetName(), init: init})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdStart(c *cli.Context) error {
|
func cmdStart(c *cli.Context) error {
|
||||||
|
@ -73,15 +70,10 @@ func cmdStart(c *cli.Context) error {
|
||||||
|
|
||||||
// Create Basecoin app
|
// Create Basecoin app
|
||||||
basecoinApp := app.NewBasecoin(eyesCli)
|
basecoinApp := app.NewBasecoin(eyesCli)
|
||||||
if c.Bool("ibc-plugin") {
|
|
||||||
basecoinApp.RegisterPlugin(ibc.New())
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop through all registered plugins and enable if desired
|
// register all plugins
|
||||||
for _, p := range plugins {
|
for _, p := range plugins {
|
||||||
if c.Bool(p.name) {
|
basecoinApp.RegisterPlugin(p.newPlugin())
|
||||||
basecoinApp.RegisterPlugin(p.init())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If genesis file exists, set key-value options
|
// If genesis file exists, set key-value options
|
|
@ -16,61 +16,56 @@ import (
|
||||||
tmtypes "github.com/tendermint/tendermint/types"
|
tmtypes "github.com/tendermint/tendermint/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var TxFlags = []cli.Flag{
|
||||||
|
NodeFlag,
|
||||||
|
ChainIDFlag,
|
||||||
|
|
||||||
|
FromFlag,
|
||||||
|
|
||||||
|
AmountFlag,
|
||||||
|
CoinFlag,
|
||||||
|
GasFlag,
|
||||||
|
FeeFlag,
|
||||||
|
SeqFlag,
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
TxCmd = cli.Command{
|
||||||
|
Name: "tx",
|
||||||
|
Usage: "Create, sign, and broadcast a transaction",
|
||||||
|
ArgsUsage: "",
|
||||||
|
Subcommands: []cli.Command{
|
||||||
|
SendTxCmd,
|
||||||
|
AppTxCmd,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
SendTxCmd = cli.Command{
|
SendTxCmd = cli.Command{
|
||||||
Name: "sendtx",
|
Name: "send",
|
||||||
Usage: "Broadcast a basecoin SendTx",
|
Usage: "Create, sign, and broadcast a SendTx transaction",
|
||||||
ArgsUsage: "",
|
ArgsUsage: "",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
return cmdSendTx(c)
|
return cmdSendTx(c)
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: append(TxFlags, ToFlag),
|
||||||
NodeFlag,
|
|
||||||
ChainIDFlag,
|
|
||||||
|
|
||||||
FromFlag,
|
|
||||||
|
|
||||||
AmountFlag,
|
|
||||||
CoinFlag,
|
|
||||||
GasFlag,
|
|
||||||
FeeFlag,
|
|
||||||
SeqFlag,
|
|
||||||
|
|
||||||
ToFlag,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AppTxCmd = cli.Command{
|
AppTxCmd = cli.Command{
|
||||||
Name: "apptx",
|
Name: "app",
|
||||||
Usage: "Broadcast a basecoin AppTx",
|
Usage: "Create, sign, and broadcast a raw AppTx transaction",
|
||||||
ArgsUsage: "",
|
ArgsUsage: "",
|
||||||
Action: func(c *cli.Context) error {
|
Action: func(c *cli.Context) error {
|
||||||
return cmdAppTx(c)
|
return cmdAppTx(c)
|
||||||
},
|
},
|
||||||
Flags: []cli.Flag{
|
Flags: append(TxFlags, NameFlag, DataFlag),
|
||||||
NodeFlag,
|
|
||||||
ChainIDFlag,
|
|
||||||
|
|
||||||
FromFlag,
|
|
||||||
|
|
||||||
AmountFlag,
|
|
||||||
CoinFlag,
|
|
||||||
GasFlag,
|
|
||||||
FeeFlag,
|
|
||||||
SeqFlag,
|
|
||||||
|
|
||||||
NameFlag,
|
|
||||||
DataFlag,
|
|
||||||
},
|
|
||||||
// Subcommands are dynamically registered with plugins as needed
|
// Subcommands are dynamically registered with plugins as needed
|
||||||
Subcommands: []cli.Command{},
|
Subcommands: []cli.Command{},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterTxPlugin is used to add another subcommand and create a custom
|
// Register a subcommand of TxCmd to craft transactions for plugins
|
||||||
// apptx encoding. Look at counter.go for an example
|
func RegisterTxSubcommand(cmd cli.Command) {
|
||||||
func RegisterTxPlugin(cmd cli.Command) {
|
TxCmd.Subcommands = append(TxCmd.Subcommands, cmd)
|
||||||
AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdSendTx(c *cli.Context) error {
|
func cmdSendTx(c *cli.Context) error {
|
||||||
|
@ -87,18 +82,17 @@ func cmdSendTx(c *cli.Context) error {
|
||||||
return errors.New("To address is invalid hex: " + err.Error())
|
return errors.New("To address is invalid hex: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the priv validator
|
// load the priv key
|
||||||
// XXX: this is overkill for now, we need a keys solution
|
privKey := LoadKey(fromFile)
|
||||||
privVal := tmtypes.LoadPrivValidator(fromFile)
|
|
||||||
|
|
||||||
// get the sequence number for the tx
|
// get the sequence number for the tx
|
||||||
sequence, err := getSeq(c, privVal.Address)
|
sequence, err := getSeq(c, privKey.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// craft the tx
|
// craft the tx
|
||||||
input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
|
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
|
||||||
output := newOutput(to, coin, amount)
|
output := newOutput(to, coin, amount)
|
||||||
tx := &types.SendTx{
|
tx := &types.SendTx{
|
||||||
Gas: int64(gas),
|
Gas: int64(gas),
|
||||||
|
@ -109,7 +103,7 @@ func cmdSendTx(c *cli.Context) error {
|
||||||
|
|
||||||
// sign that puppy
|
// sign that puppy
|
||||||
signBytes := tx.SignBytes(chainID)
|
signBytes := tx.SignBytes(chainID)
|
||||||
tx.Inputs[0].Signature = privVal.Sign(signBytes)
|
tx.Inputs[0].Signature = privKey.Sign(signBytes)
|
||||||
|
|
||||||
fmt.Println("Signed SendTx:")
|
fmt.Println("Signed SendTx:")
|
||||||
fmt.Println(string(wire.JSONBytes(tx)))
|
fmt.Println(string(wire.JSONBytes(tx)))
|
||||||
|
@ -139,14 +133,14 @@ func AppTx(c *cli.Context, name string, data []byte) error {
|
||||||
gas, fee := c.Int("gas"), int64(c.Int("fee"))
|
gas, fee := c.Int("gas"), int64(c.Int("fee"))
|
||||||
chainID := c.String("chain_id")
|
chainID := c.String("chain_id")
|
||||||
|
|
||||||
privVal := tmtypes.LoadPrivValidator(fromFile)
|
privKey := tmtypes.LoadPrivValidator(fromFile)
|
||||||
|
|
||||||
sequence, err := getSeq(c, privVal.Address)
|
sequence, err := getSeq(c, privKey.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
|
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
|
||||||
tx := &types.AppTx{
|
tx := &types.AppTx{
|
||||||
Gas: int64(gas),
|
Gas: int64(gas),
|
||||||
Fee: types.Coin{coin, fee},
|
Fee: types.Coin{coin, fee},
|
||||||
|
@ -155,7 +149,7 @@ func AppTx(c *cli.Context, name string, data []byte) error {
|
||||||
Data: data,
|
Data: data,
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID))
|
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID))
|
||||||
|
|
||||||
fmt.Println("Signed AppTx:")
|
fmt.Println("Signed AppTx:")
|
||||||
fmt.Println(string(wire.JSONBytes(tx)))
|
fmt.Println(string(wire.JSONBytes(tx)))
|
|
@ -1,48 +1,46 @@
|
||||||
package commands
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/tendermint/basecoin/plugins/counter"
|
|
||||||
"github.com/tendermint/basecoin/types"
|
|
||||||
wire "github.com/tendermint/go-wire"
|
wire "github.com/tendermint/go-wire"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
CounterTxCmd = cli.Command{
|
"github.com/tendermint/basecoin/plugins/counter"
|
||||||
Name: "counter",
|
"github.com/tendermint/basecoin/types"
|
||||||
Usage: "Craft a transaction to the counter plugin",
|
|
||||||
Action: func(c *cli.Context) error {
|
|
||||||
return cmdCounterTx(c)
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
ValidFlag,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
CounterPluginFlag = cli.BoolFlag{
|
|
||||||
Name: "counter-plugin",
|
|
||||||
Usage: "Enable the counter plugin",
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RegisterTxPlugin(CounterTxCmd)
|
commands.RegisterTxSubcommand(CounterTxCmd)
|
||||||
RegisterStartPlugin(CounterPluginFlag,
|
commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() })
|
||||||
func() types.Plugin { return counter.New("counter") })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ValidFlag = cli.BoolFlag{
|
||||||
|
Name: "valid",
|
||||||
|
Usage: "Set valid field in CounterTx",
|
||||||
|
}
|
||||||
|
|
||||||
|
CounterTxCmd = cli.Command{
|
||||||
|
Name: "counter",
|
||||||
|
Usage: "Create, sign, and broadcast a transaction to the counter plugin",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return cmdCounterTx(c)
|
||||||
|
},
|
||||||
|
Flags: append(commands.TxFlags, ValidFlag),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
func cmdCounterTx(c *cli.Context) error {
|
func cmdCounterTx(c *cli.Context) error {
|
||||||
valid := c.Bool("valid")
|
valid := c.Bool("valid")
|
||||||
parent := c.Parent()
|
|
||||||
|
|
||||||
counterTx := counter.CounterTx{
|
counterTx := counter.CounterTx{
|
||||||
Valid: valid,
|
Valid: valid,
|
||||||
Fee: types.Coins{
|
Fee: types.Coins{
|
||||||
{
|
{
|
||||||
Denom: parent.String("coin"),
|
Denom: c.String("coin"),
|
||||||
Amount: int64(parent.Int("fee")),
|
Amount: int64(c.Int("fee")),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -52,5 +50,5 @@ func cmdCounterTx(c *cli.Context) error {
|
||||||
data := wire.BinaryBytes(counterTx)
|
data := wire.BinaryBytes(counterTx)
|
||||||
name := "counter"
|
name := "counter"
|
||||||
|
|
||||||
return AppTx(parent, name, data)
|
return commands.AppTx(c, name, data)
|
||||||
}
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "counter"
|
||||||
|
app.Usage = "counter [command] [args...]"
|
||||||
|
app.Version = "0.1.0"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
commands.StartCmd,
|
||||||
|
commands.TxCmd,
|
||||||
|
commands.KeyCmd,
|
||||||
|
commands.QueryCmd,
|
||||||
|
commands.AccountCmd,
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
"base/chainID", "test_chain_id",
|
"base/chainID", "test_chain_id",
|
||||||
"base/account", {
|
"base/account", {
|
||||||
"pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"],
|
"pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"],
|
||||||
"coins": [
|
"coins": [
|
||||||
{
|
{
|
||||||
"denom": "blank",
|
"denom": "blank",
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
|
||||||
|
"priv_key": [
|
||||||
|
1,
|
||||||
|
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
],
|
||||||
|
"pub_key": [
|
||||||
|
1,
|
||||||
|
"619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
|
||||||
|
"priv_key": [
|
||||||
|
1,
|
||||||
|
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
],
|
||||||
|
"pub_key": [
|
||||||
|
1,
|
||||||
|
"352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"address": "4793A333846E5104C46DD9AB9A00E31821B2F301",
|
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"last_signbytes": "",
|
|
||||||
"last_step": 0,
|
|
||||||
"priv_key": [
|
|
||||||
1,
|
|
||||||
"13A04A552ABAA2CCFA1F618CF9C97F1FD59FC3EE4968FE87DF3637C9B0F2FAAA93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E"
|
|
||||||
],
|
|
||||||
"pub_key": [
|
|
||||||
1,
|
|
||||||
"93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E"
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
#! /bin/bash
|
#! /bin/bash
|
||||||
|
|
||||||
killall -9 basecoin tendermint
|
killall -9 adam tendermint
|
||||||
TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all
|
TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all
|
||||||
TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all
|
TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
{
|
{
|
||||||
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
|
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"last_signbytes": "",
|
|
||||||
"last_step": 0,
|
|
||||||
"priv_key": [
|
"priv_key": [
|
||||||
1,
|
1,
|
||||||
"39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
"39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
|
@ -1,17 +0,0 @@
|
||||||
{
|
|
||||||
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
|
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"last_signbytes": "",
|
|
||||||
"last_step": 0,
|
|
||||||
"priv_key": [
|
|
||||||
1,
|
|
||||||
"39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
|
||||||
],
|
|
||||||
"pub_key": [
|
|
||||||
1,
|
|
||||||
"B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
{
|
{
|
||||||
"address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47",
|
"address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47",
|
||||||
"last_height": 0,
|
|
||||||
"last_round": 0,
|
|
||||||
"last_signature": null,
|
|
||||||
"last_signbytes": "",
|
|
||||||
"last_step": 0,
|
|
||||||
"priv_key": [
|
"priv_key": [
|
||||||
1,
|
1,
|
||||||
"22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
|
"22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
|
|
@ -18,19 +18,19 @@ echo "CHAIN_ID1: $CHAIN_ID1"
|
||||||
echo "CHAIN_ID2: $CHAIN_ID2"
|
echo "CHAIN_ID2: $CHAIN_ID2"
|
||||||
|
|
||||||
# make reusable chain flags
|
# make reusable chain flags
|
||||||
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json"
|
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json"
|
||||||
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657"
|
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... starting chains"
|
echo "... starting chains"
|
||||||
echo ""
|
echo ""
|
||||||
# start the first node
|
# start the first node
|
||||||
TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log &
|
TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log &
|
||||||
basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log &
|
adam start --dir ./data/chain1/basecoin &> chain1_basecoin.log &
|
||||||
|
|
||||||
# start the second node
|
# start the second node
|
||||||
TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log &
|
TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log &
|
||||||
basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log &
|
adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log &
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... waiting for chains to start"
|
echo "... waiting for chains to start"
|
||||||
|
@ -40,20 +40,20 @@ sleep 10
|
||||||
echo "... registering chain1 on chain2"
|
echo "... registering chain1 on chain2"
|
||||||
echo ""
|
echo ""
|
||||||
# register chain1 on chain2
|
# register chain1 on chain2
|
||||||
basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... creating egress packet on chain1"
|
echo "... creating egress packet on chain1"
|
||||||
echo ""
|
echo ""
|
||||||
# create a packet on chain1 destined for chain2
|
# create a packet on chain1 destined for chain2
|
||||||
PAYLOAD="DEADBEEF" #TODO
|
PAYLOAD="DEADBEEF" #TODO
|
||||||
basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1
|
adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... querying for packet data"
|
echo "... querying for packet data"
|
||||||
echo ""
|
echo ""
|
||||||
# query for the packet data and proof
|
# query for the packet data and proof
|
||||||
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1)
|
QUERY_RESULT=$(adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1)
|
||||||
HEIGHT=$(echo $QUERY_RESULT | jq .height)
|
HEIGHT=$(echo $QUERY_RESULT | jq .height)
|
||||||
PACKET=$(echo $QUERY_RESULT | jq .value)
|
PACKET=$(echo $QUERY_RESULT | jq .value)
|
||||||
PROOF=$(echo $QUERY_RESULT | jq .proof)
|
PROOF=$(echo $QUERY_RESULT | jq .proof)
|
||||||
|
@ -75,7 +75,7 @@ echo ""
|
||||||
echo "... querying for block data"
|
echo "... querying for block data"
|
||||||
echo ""
|
echo ""
|
||||||
# get the header and commit for the height
|
# get the header and commit for the height
|
||||||
HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
|
HEADER_AND_COMMIT=$(adam block $HEIGHT)
|
||||||
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
|
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
|
||||||
HEADER=$(removeQuotes $HEADER)
|
HEADER=$(removeQuotes $HEADER)
|
||||||
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
|
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
|
||||||
|
@ -89,16 +89,19 @@ echo ""
|
||||||
echo "... updating state of chain1 on chain2"
|
echo "... updating state of chain1 on chain2"
|
||||||
echo ""
|
echo ""
|
||||||
# update the state of chain1 on chain2
|
# update the state of chain1 on chain2
|
||||||
basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... posting packet from chain1 on chain2"
|
echo "... posting packet from chain1 on chain2"
|
||||||
echo ""
|
echo ""
|
||||||
# post the packet from chain1 to chain2
|
# post the packet from chain1 to chain2
|
||||||
basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "... checking if the packet is present on chain2"
|
echo "... checking if the packet is present on chain2"
|
||||||
echo ""
|
echo ""
|
||||||
# query for the packet on chain2 !
|
# query for the packet on chain2 !
|
||||||
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1
|
adam query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "DONE!"
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
Make sure you have [basecoin installed](install.md).
|
||||||
|
You will also need to [install tendermint](https://tendermint.com/intro/getting-started/download).
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
|
||||||
|
Basecoin is an ABCI application that runs on Tendermint, so we first need to initialize Tendermint:
|
||||||
|
|
||||||
|
```
|
||||||
|
tendermint init
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create the necessary files for a single Tendermint node in `~/.tendermint`.
|
||||||
|
If you had previously run tendermint, make sure you reset the chain
|
||||||
|
(note this will delete all chain data, so back it up if you need it):
|
||||||
|
|
||||||
|
```
|
||||||
|
tendermint unsafe_reset_all
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we need some initialization files for basecoin.
|
||||||
|
We have included some defaults in the basecoin directory, under `data`.
|
||||||
|
For purposes of convenience, change to that directory:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd $GOPATH/src/github.com/tendermint/basecoin/data
|
||||||
|
```
|
||||||
|
|
||||||
|
The directory contains a genesis file and two private keys.
|
||||||
|
|
||||||
|
You can generate your own private keys with `tendermint gen_validator`,
|
||||||
|
and construct the `genesis.json` as you like.
|
||||||
|
Note, however, that you must be careful with the `chain_id` field,
|
||||||
|
as every transaction must contain the correct `chain_id`
|
||||||
|
(default is `test_chain_id`).
|
||||||
|
|
||||||
|
## Start
|
||||||
|
|
||||||
|
Now we can start basecoin:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin start --in-proc
|
||||||
|
```
|
||||||
|
|
||||||
|
This will initialize the chain with the `genesis.json` file from the current directory. If you want to specify another location, you can run:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin start --in-proc --dir PATH/TO/CUSTOM/DATA
|
||||||
|
```
|
||||||
|
|
||||||
|
Note that `--in-proc` stands for "in process", which means
|
||||||
|
basecoin will be started with the Tendermint node running in the same process.
|
||||||
|
To start Tendermint in a separate process instead, use:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin start
|
||||||
|
```
|
||||||
|
|
||||||
|
and in another window:
|
||||||
|
|
||||||
|
```
|
||||||
|
tendermint node
|
||||||
|
```
|
||||||
|
|
||||||
|
In either case, you should see blocks start streaming in!
|
||||||
|
Note, however, that currently basecoin currently requires the
|
||||||
|
`develop` branch of tendermint for this to work.
|
||||||
|
|
||||||
|
## Send transactions
|
||||||
|
|
||||||
|
Now we are ready to send some transactions.
|
||||||
|
If you take a look at the `genesis.json` file, you will see one account listed there.
|
||||||
|
This account corresponds to the private key in `key.json`.
|
||||||
|
We also included the private key for another account, in `key2.json`.
|
||||||
|
|
||||||
|
Let's check the balance of these two accounts:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin account 0x1B1BE55F969F54064628A63B9559E7C21C925165
|
||||||
|
basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090
|
||||||
|
```
|
||||||
|
|
||||||
|
The first account is flush with cash, while the second account doesn't exist.
|
||||||
|
Let's send funds from the first account to the second:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10
|
||||||
|
```
|
||||||
|
|
||||||
|
By default, the CLI looks for a `priv_validator.json` to sign the transaction with,
|
||||||
|
so this will only work if you are in the `$GOPATH/src/github.com/tendermint/basecoin/data`.
|
||||||
|
To specify a different key, we can use the `--from` flag.
|
||||||
|
|
||||||
|
Now if we check the second account, it should have `10` coins!
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090
|
||||||
|
```
|
||||||
|
|
||||||
|
We can send some of these coins back like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how we use the `--from` flag to select a different account to send from.
|
||||||
|
|
||||||
|
If we try to send too much, we'll get an error:
|
||||||
|
|
||||||
|
```
|
||||||
|
basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100
|
||||||
|
```
|
||||||
|
|
||||||
|
See `basecoin tx send --help` for additional details.
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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)
|
|
@ -0,0 +1,87 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
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 derrived 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.
|
||||||
|
|
||||||
|
## 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,7 @@
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
Up until this point, we have only been testing the code as a stand-alone abci app, which is nice for developing, but it is no blockchain. Just a blockchain-ready application.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
**TODO** Maybe we link to a blog post for this???
|
|
@ -0,0 +1,420 @@
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
The `main.go` is very simple and does not need to be changed:
|
||||||
|
|
||||||
|
```
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "example-plugin"
|
||||||
|
app.Usage = "example-plugin [command] [args...]"
|
||||||
|
app.Version = "0.1.0"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
commands.StartCmd,
|
||||||
|
commands.TxCmd,
|
||||||
|
commands.KeyCmd,
|
||||||
|
commands.QueryCmd,
|
||||||
|
commands.AccountCmd,
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
First, we register the plugin:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
func init() {
|
||||||
|
commands.RegisterTxSubcommand(ExamplePluginTxCmd)
|
||||||
|
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a new subcommand under `tx` (defined below),
|
||||||
|
and ensures the plugin is activated when we start the app.
|
||||||
|
Now we actually define the new command:
|
||||||
|
|
||||||
|
```
|
||||||
|
var (
|
||||||
|
ExampleFlag = cli.BoolFlag{
|
||||||
|
Name: "valid",
|
||||||
|
Usage: "Set this to make the transaction valid",
|
||||||
|
}
|
||||||
|
|
||||||
|
ExamplePluginTxCmd = cli.Command{
|
||||||
|
Name: "example",
|
||||||
|
Usage: "Create, sign, and broadcast a transaction to the example plugin",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return cmdExamplePluginTx(c)
|
||||||
|
},
|
||||||
|
Flags: append(commands.TxFlags, ExampleFlag),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
Flags: append(commands.TxFlags, ExampleFlag),
|
||||||
|
```
|
||||||
|
|
||||||
|
The `commands.TxFlags` is defined in `cmd/commands/tx.go`:
|
||||||
|
|
||||||
|
```
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||||
|
go install ./docs/guide/src/example-plugin
|
||||||
|
example-plugin tx example --help
|
||||||
|
```
|
||||||
|
|
||||||
|
The output:
|
||||||
|
|
||||||
|
```
|
||||||
|
NAME:
|
||||||
|
example-plugin tx example - Create, sign, and broadcast a transaction to the example plugin
|
||||||
|
|
||||||
|
USAGE:
|
||||||
|
example-plugin tx example [command options] [arguments...]
|
||||||
|
|
||||||
|
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 Amount of coins to send in the transaction (default: 0)
|
||||||
|
--coin value Specify a coin denomination (default: "blank")
|
||||||
|
--gas value The amount of gas for the transaction (default: 0)
|
||||||
|
--fee value The transaction fee (default: 0)
|
||||||
|
--sequence value Sequence number for the account (default: 0)
|
||||||
|
--valid Set this to make the transaction valid
|
||||||
|
```
|
||||||
|
|
||||||
|
Cool, eh?
|
||||||
|
|
||||||
|
Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`:
|
||||||
|
|
||||||
|
```
|
||||||
|
func cmdExamplePluginTx(c *cli.Context) error {
|
||||||
|
exampleFlag := c.Bool("valid")
|
||||||
|
exampleTx := ExamplePluginTx{exampleFlag}
|
||||||
|
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## RunTx
|
||||||
|
|
||||||
|
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 plugins.
|
||||||
|
Here's what's relevant for us:
|
||||||
|
|
||||||
|
```
|
||||||
|
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 tx
|
||||||
|
var tx ExamplePluginTx
|
||||||
|
err := wire.ReadBinaryBytes(txBytes, &tx)
|
||||||
|
if err != nil {
|
||||||
|
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate tx
|
||||||
|
if !tx.Valid {
|
||||||
|
return abci.ErrInternalError.AppendLog("Valid must be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PluginState
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//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:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
// Decode tx
|
||||||
|
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 currectly, we can now check if it's valid:
|
||||||
|
|
||||||
|
```
|
||||||
|
// 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Load PluginState
|
||||||
|
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`.
|
||||||
|
Finally, we can update the state's `Counter`, and save the state back to the store:
|
||||||
|
|
||||||
|
```
|
||||||
|
//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
|
||||||
|
|
||||||
|
In the [previous tutorial](basecoin-basics.md),
|
||||||
|
we used a pre-generated `genesis.json` and `priv_validator.json` for the application.
|
||||||
|
This time, let's make our own.
|
||||||
|
|
||||||
|
First, let's create a new directory and change into it:
|
||||||
|
|
||||||
|
```
|
||||||
|
mkdir example-data
|
||||||
|
cd example-data
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, let's create a new private key:
|
||||||
|
|
||||||
|
```
|
||||||
|
example-plugin key new > key.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Here's what my `key.json looks like:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"address": "15F591CA434CFCCBDEC1D206F3ED3EBA207BFE7D",
|
||||||
|
"priv_key": [
|
||||||
|
1,
|
||||||
|
"737C629667A9EAADBB8E7CF792D5A8F63AA4BB51E06457DDD7FDCC6D7412AAAD43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3"
|
||||||
|
],
|
||||||
|
"pub_key": [
|
||||||
|
1,
|
||||||
|
"43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can make a `genesis.json` file and add an account with out public key:
|
||||||
|
|
||||||
|
```
|
||||||
|
[
|
||||||
|
"base/chainID", "example-chain",
|
||||||
|
"base/account", {
|
||||||
|
"pub_key": [1, "43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3"],
|
||||||
|
"coins": [
|
||||||
|
{
|
||||||
|
"denom": "gold",
|
||||||
|
"amount": 1000000000,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Here we've granted ourselves `1000000000` units of the `gold` token.
|
||||||
|
|
||||||
|
Before we can start the blockchain, we must initialize and/or reset the tendermint state for a new blockchain:
|
||||||
|
|
||||||
|
```
|
||||||
|
tendermint init
|
||||||
|
tendermint unsafe_reset_all
|
||||||
|
```
|
||||||
|
|
||||||
|
Great, now we're ready to go.
|
||||||
|
To start the blockchain, simply run
|
||||||
|
|
||||||
|
```
|
||||||
|
example-plugin start --in-proc
|
||||||
|
```
|
||||||
|
|
||||||
|
In another window, we can try sending some transactions:
|
||||||
|
|
||||||
|
```
|
||||||
|
example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount 100 --coin gold --chain_id example-chain
|
||||||
|
```
|
||||||
|
|
||||||
|
Note the `--coin` and `--chain_id` flags. In the [previous tutorial](basecoin-basics.md),
|
||||||
|
we didn't need them because we were using the default coin type ("blank") and chain ID ("test_chain_id").
|
||||||
|
Now that we're using custom values, we need to specify them explicitly on the command line.
|
||||||
|
|
||||||
|
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 1 --coin gold --chain_id example-chain
|
||||||
|
```
|
||||||
|
|
||||||
|
The transaction is invalid! That's because we didn't specify the `--valid` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain
|
||||||
|
```
|
||||||
|
|
||||||
|
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 1 --coin gold --chain_id example-chain
|
||||||
|
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 Stpes
|
||||||
|
|
||||||
|
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,
|
||||||
|
adding features for minting new coins, voting, and changin the Tendermint validator set.
|
||||||
|
But first, you may want to learn a bit more about [the design of the plugin system](plugin-design.md)
|
|
@ -0,0 +1,301 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
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/plugin-design.md)
|
||||||
|
and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works.
|
||||||
|
You may also want to see the tutorials on [a simple example plugin](example-plugin.md)
|
||||||
|
and the list of [more advanced plugins](more-examples.md).
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
||||||
|
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 some state from height H, we need the signatures and root
|
||||||
|
hash from the header at height 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
|
||||||
|
to the latest header available, so long as the validator set has not changed
|
||||||
|
much. If the validator set is changing, the client needs to track these
|
||||||
|
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`.
|
||||||
|
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.
|
||||||
|
```
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
type IBCRegisterChainTx struct {
|
||||||
|
BlockchainGenesis
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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 (ie. an integer that increments with every message sent between this pair of chains),
|
||||||
|
a packet type (eg. coin, data, etc.),
|
||||||
|
and a payload.
|
||||||
|
|
||||||
|
```
|
||||||
|
type IBCPacketCreateTx struct {
|
||||||
|
Packet
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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.
|
||||||
|
|
||||||
|
### 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:
|
||||||
|
|
||||||
|
```
|
||||||
|
- Genesis configuration
|
||||||
|
- Latest state
|
||||||
|
- Headers for recent heights
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Make sure you have installed
|
||||||
|
[tendermint](https://tendermint.com/intro/getting-started/download) and
|
||||||
|
[adam](/docs/guide/install.md).
|
||||||
|
|
||||||
|
`adam` is the name for the program that will become the Cosmos Hub.
|
||||||
|
We call it Adam because it's the first blockchain in [the Cosmos Network](https://cosmos.network).
|
||||||
|
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
The relevant data is now in the `data` directory.
|
||||||
|
|
||||||
|
We can start the two chains as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log &
|
||||||
|
adam start --dir ./data/chain1/basecoin &> chain1_adam.log &
|
||||||
|
```
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
```
|
||||||
|
TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log &
|
||||||
|
adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log &
|
||||||
|
```
|
||||||
|
|
||||||
|
Note how we refer to the relevant data directories. Also note how we have to 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
|
||||||
|
```
|
||||||
|
|
||||||
|
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`.
|
||||||
|
For the sake of convenience, let's first set some environment variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
export CHAIN_ID1=test_chain_1
|
||||||
|
export CHAIN_ID2=test_chain_2
|
||||||
|
|
||||||
|
export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json"
|
||||||
|
export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657"
|
||||||
|
```
|
||||||
|
|
||||||
|
Let's start by registering `test_chain_1` on `test_chain_2`:
|
||||||
|
|
||||||
|
```
|
||||||
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we can create the outgoing packet on `test_chain_1`:
|
||||||
|
|
||||||
|
```
|
||||||
|
adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1
|
||||||
|
```
|
||||||
|
|
||||||
|
Note our payload is just `DEADBEEF`.
|
||||||
|
Now that the packet is committed in the chain, let's get some proof by querying:
|
||||||
|
|
||||||
|
```
|
||||||
|
adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1
|
||||||
|
```
|
||||||
|
|
||||||
|
The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet),
|
||||||
|
and a proof (ie. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree.
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
adam block <height>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<height>` is the height returned in the previous 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!
|
||||||
|
|
||||||
|
Let's send this updated information about `test_chain_1` to `test_chain_2`:
|
||||||
|
|
||||||
|
```
|
||||||
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x<header>--commit 0x<commit>
|
||||||
|
```
|
||||||
|
|
||||||
|
where `<header>` and `<commit>` are the hex-encoded header and commit returned by the previous `block` command.
|
||||||
|
|
||||||
|
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!
|
||||||
|
|
||||||
|
```
|
||||||
|
adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height <height + 1> --packet 0x<packet> --proof 0x<proof>
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `<height + 1>` is one greater than the height retuned by the previous `query` command, and `<packet>` and `<proof>` are the
|
||||||
|
`value` and `proof` returned in that same query.
|
||||||
|
|
||||||
|
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 (ie. 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!
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Install
|
||||||
|
|
||||||
|
We use glide for dependency management. The prefered way of compiling from source is the following:
|
||||||
|
|
||||||
|
```
|
||||||
|
go get -d github.com/tendermint/basecoin/cmd/basecoin
|
||||||
|
cd $GOPATH/src/github.com/tendermint/basecoin
|
||||||
|
make get_vendor_deps
|
||||||
|
make install
|
||||||
|
```
|
||||||
|
|
||||||
|
This will create the `basecoin` binary in `$GOPATH/bin`.
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Plugin Examples
|
||||||
|
|
||||||
|
Now that we've seen [how to write a simple plugin](example-plugin.md)
|
||||||
|
and taken a look at [how the plugin system is designed](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][0] - a plugin for issuing new Basecoin tokens
|
||||||
|
2. [Trader][1] - a plugin for adding escrow and options features to Basecoin
|
||||||
|
3. [Stakecoin][2] - a plugin for bonding and unbonding Tendermint validators and updating the validator set accordingly
|
||||||
|
4. [PayToVote][3] - a plugin for creating issues and voting on them
|
||||||
|
5. [IBC][4] - a plugin for facilitating InterBlockchain Communication
|
||||||
|
[0]: https://github.com/tendermint/basecoin-examples/tree/develop/mintcoin
|
||||||
|
[1]: https://github.com/tendermint/basecoin-examples/tree/develop/trader
|
||||||
|
[2]: https://github.com/tendermint/basecoin-examples/tree/develop/stake
|
||||||
|
[3]: https://github.com/tendermint/basecoin-examples/tree/develop/paytovote
|
||||||
|
[4]: ibc.md
|
|
@ -0,0 +1,71 @@
|
||||||
|
# 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`:
|
||||||
|
|
||||||
|
```
|
||||||
|
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 transasaction,
|
||||||
|
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:
|
||||||
|
|
||||||
|
```
|
||||||
|
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, height uint64)
|
||||||
|
EndBlock(store KVStore, height uint64) []*abci.Validator
|
||||||
|
}
|
||||||
|
|
||||||
|
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-derrived cryptocurrency can be greatly extended.
|
||||||
|
One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin!
|
||||||
|
|
||||||
|
## 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).
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
wire "github.com/tendermint/go-wire"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
commands.RegisterTxSubcommand(ExamplePluginTxCmd)
|
||||||
|
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ExampleFlag = cli.BoolFlag{
|
||||||
|
Name: "valid",
|
||||||
|
Usage: "Set this to make the transaction valid",
|
||||||
|
}
|
||||||
|
|
||||||
|
ExamplePluginTxCmd = cli.Command{
|
||||||
|
Name: "example",
|
||||||
|
Usage: "Create, sign, and broadcast a transaction to the example plugin",
|
||||||
|
Action: func(c *cli.Context) error {
|
||||||
|
return cmdExamplePluginTx(c)
|
||||||
|
},
|
||||||
|
Flags: append(commands.TxFlags, ExampleFlag),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdExamplePluginTx(c *cli.Context) error {
|
||||||
|
exampleFlag := c.Bool("valid")
|
||||||
|
exampleTx := ExamplePluginTx{exampleFlag}
|
||||||
|
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/tendermint/basecoin/cmd/commands"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
app := cli.NewApp()
|
||||||
|
app.Name = "example-plugin"
|
||||||
|
app.Usage = "example-plugin [command] [args...]"
|
||||||
|
app.Version = "0.1.0"
|
||||||
|
app.Commands = []cli.Command{
|
||||||
|
commands.StartCmd,
|
||||||
|
commands.TxCmd,
|
||||||
|
commands.KeyCmd,
|
||||||
|
commands.QueryCmd,
|
||||||
|
commands.AccountCmd,
|
||||||
|
}
|
||||||
|
app.Run(os.Args)
|
||||||
|
}
|
|
@ -0,0 +1,80 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
abci "github.com/tendermint/abci/types"
|
||||||
|
"github.com/tendermint/basecoin/types"
|
||||||
|
"github.com/tendermint/go-wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
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 tx
|
||||||
|
var tx ExamplePluginTx
|
||||||
|
err := wire.ReadBinaryBytes(txBytes, &tx)
|
||||||
|
if err != nil {
|
||||||
|
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate tx
|
||||||
|
if !tx.Valid {
|
||||||
|
return abci.ErrInternalError.AppendLog("Valid must be true")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load PluginState
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//App Logic
|
||||||
|
pluginState.Counter += 1
|
||||||
|
|
||||||
|
// Save PluginState
|
||||||
|
store.Set(ep.StateKey(), wire.BinaryBytes(pluginState))
|
||||||
|
|
||||||
|
return abci.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep *ExamplePlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep *ExamplePlugin) BeginBlock(store types.KVStore, height uint64) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -32,9 +32,9 @@ func (cp *CounterPlugin) StateKey() []byte {
|
||||||
return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name))
|
return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(name string) *CounterPlugin {
|
func New() *CounterPlugin {
|
||||||
return &CounterPlugin{
|
return &CounterPlugin{
|
||||||
name: name,
|
name: "counter",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue