Merge pull request #22 from tendermint/docs

Docs
This commit is contained in:
Ethan Buchman 2017-02-07 20:41:04 -05:00 committed by GitHub
commit a50146d850
34 changed files with 1543 additions and 401 deletions

View File

@ -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.

View File

@ -2,20 +2,16 @@
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.
2. As a framework for anyone wishing to build a tendermint-based currency, extensible using the plugin system.
Basecoin serves as a reference implementation for how we build ABCI applications in Go,
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.
## 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)
WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready.
## Installation
@ -30,49 +26,21 @@ make install
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.
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`),
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`).
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`.
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)

30
cmd/adam/main.go Normal file
View File

@ -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)
}

View File

@ -3,7 +3,7 @@ package main
import (
"os"
"github.com/tendermint/basecoin/cmd/basecoin/commands"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/urfave/cli"
)
@ -14,12 +14,11 @@ func main() {
app.Version = "0.1.0"
app.Commands = []cli.Command{
commands.StartCmd,
commands.SendTxCmd,
commands.AppTxCmd,
commands.IbcCmd,
commands.TxCmd,
commands.QueryCmd,
commands.VerifyCmd,
commands.BlockCmd,
commands.KeyCmd,
commands.VerifyCmd, // TODO: move to merkleeyes?
commands.BlockCmd, // TODO: move to adam?
commands.AccountCmd,
}
app.Run(os.Args)

View File

@ -31,11 +31,6 @@ var (
Name: "in-proc",
Usage: "Run Tendermint in-process with the App",
}
IbcPluginFlag = cli.BoolFlag{
Name: "ibc-plugin",
Usage: "Enable the ibc plugin",
}
)
// tx flags
@ -61,7 +56,7 @@ var (
FromFlag = cli.StringFlag{
Name: "from",
Value: "priv_validator.json",
Value: "key.json",
Usage: "Path to a private key to sign the transaction",
}
@ -106,86 +101,6 @@ var (
Value: "test_chain_id",
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

View File

@ -9,6 +9,7 @@ import (
"github.com/urfave/cli"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-merkle"
@ -16,25 +17,97 @@ import (
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 (
IbcCmd = cli.Command{
Name: "ibc",
Usage: "Send a transaction to the interblockchain (ibc) plugin",
Flags: []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
NameFlag,
DataFlag,
},
Flags: TxFlags,
Subcommands: []cli.Command{
IbcRegisterTxCmd,
IbcUpdateTxCmd,
@ -69,9 +142,6 @@ var (
IbcPacketTxCmd = cli.Command{
Name: "packet",
Usage: "Send a new packet via IBC",
Flags: []cli.Flag{
//
},
Subcommands: []cli.Command{
IbcPacketCreateTx,
IbcPacketPostTx,
@ -108,6 +178,9 @@ var (
}
)
//---------------------------------------------------------------------
// ibc command implementations
func cmdIBCRegisterTx(c *cli.Context) error {
chainID := c.String("chain_id")
genesisFile := c.String("genesis")

73
cmd/commands/key.go Normal file
View File

@ -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
}

View File

@ -19,7 +19,6 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
)
@ -40,21 +39,19 @@ var StartCmd = cli.Command{
DirFlag,
InProcTMFlag,
ChainIDFlag,
IbcPluginFlag,
},
}
type plugin struct {
name string
init func() types.Plugin
name string
newPlugin func() types.Plugin
}
var plugins = []plugin{}
// RegisterStartPlugin is used to add another
func RegisterStartPlugin(flag cli.BoolFlag, init func() types.Plugin) {
StartCmd.Flags = append(StartCmd.Flags, flag)
plugins = append(plugins, plugin{name: flag.GetName(), init: init})
// RegisterStartPlugin is used to enable a plugin
func RegisterStartPlugin(name string, newPlugin func() types.Plugin) {
plugins = append(plugins, plugin{name: name, newPlugin: newPlugin})
}
func cmdStart(c *cli.Context) error {
@ -73,15 +70,10 @@ func cmdStart(c *cli.Context) error {
// Create Basecoin app
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 {
if c.Bool(p.name) {
basecoinApp.RegisterPlugin(p.init())
}
basecoinApp.RegisterPlugin(p.newPlugin())
}
// If genesis file exists, set key-value options

View File

@ -16,61 +16,56 @@ import (
tmtypes "github.com/tendermint/tendermint/types"
)
var TxFlags = []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
}
var (
TxCmd = cli.Command{
Name: "tx",
Usage: "Create, sign, and broadcast a transaction",
ArgsUsage: "",
Subcommands: []cli.Command{
SendTxCmd,
AppTxCmd,
},
}
SendTxCmd = cli.Command{
Name: "sendtx",
Usage: "Broadcast a basecoin SendTx",
Name: "send",
Usage: "Create, sign, and broadcast a SendTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdSendTx(c)
},
Flags: []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
ToFlag,
},
Flags: append(TxFlags, ToFlag),
}
AppTxCmd = cli.Command{
Name: "apptx",
Usage: "Broadcast a basecoin AppTx",
Name: "app",
Usage: "Create, sign, and broadcast a raw AppTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdAppTx(c)
},
Flags: []cli.Flag{
NodeFlag,
ChainIDFlag,
FromFlag,
AmountFlag,
CoinFlag,
GasFlag,
FeeFlag,
SeqFlag,
NameFlag,
DataFlag,
},
Flags: append(TxFlags, NameFlag, DataFlag),
// Subcommands are dynamically registered with plugins as needed
Subcommands: []cli.Command{},
}
)
// RegisterTxPlugin is used to add another subcommand and create a custom
// apptx encoding. Look at counter.go for an example
func RegisterTxPlugin(cmd cli.Command) {
AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd)
// Register a subcommand of TxCmd to craft transactions for plugins
func RegisterTxSubcommand(cmd cli.Command) {
TxCmd.Subcommands = append(TxCmd.Subcommands, cmd)
}
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())
}
// load the priv validator
// XXX: this is overkill for now, we need a keys solution
privVal := tmtypes.LoadPrivValidator(fromFile)
// load the priv key
privKey := LoadKey(fromFile)
// get the sequence number for the tx
sequence, err := getSeq(c, privVal.Address)
sequence, err := getSeq(c, privKey.Address)
if err != nil {
return err
}
// 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)
tx := &types.SendTx{
Gas: int64(gas),
@ -109,7 +103,7 @@ func cmdSendTx(c *cli.Context) error {
// sign that puppy
signBytes := tx.SignBytes(chainID)
tx.Inputs[0].Signature = privVal.Sign(signBytes)
tx.Inputs[0].Signature = privKey.Sign(signBytes)
fmt.Println("Signed SendTx:")
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"))
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 {
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{
Gas: int64(gas),
Fee: types.Coin{coin, fee},
@ -155,7 +149,7 @@ func AppTx(c *cli.Context, name string, data []byte) error {
Data: data,
}
tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID))
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID))
fmt.Println("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx)))

View File

@ -1,48 +1,46 @@
package commands
package main
import (
"fmt"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/types"
wire "github.com/tendermint/go-wire"
"github.com/urfave/cli"
)
var (
CounterTxCmd = cli.Command{
Name: "counter",
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",
}
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/types"
)
func init() {
RegisterTxPlugin(CounterTxCmd)
RegisterStartPlugin(CounterPluginFlag,
func() types.Plugin { return counter.New("counter") })
commands.RegisterTxSubcommand(CounterTxCmd)
commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() })
}
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 {
valid := c.Bool("valid")
parent := c.Parent()
counterTx := counter.CounterTx{
Valid: valid,
Fee: types.Coins{
{
Denom: parent.String("coin"),
Amount: int64(parent.Int("fee")),
Denom: c.String("coin"),
Amount: int64(c.Int("fee")),
},
},
}
@ -52,5 +50,5 @@ func cmdCounterTx(c *cli.Context) error {
data := wire.BinaryBytes(counterTx)
name := "counter"
return AppTx(parent, name, data)
return commands.AppTx(c, name, data)
}

23
cmd/counter/main.go Normal file
View File

@ -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)
}

View File

@ -1,7 +1,7 @@
[
"base/chainID", "test_chain_id",
"base/account", {
"pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"],
"pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"],
"coins": [
{
"denom": "blank",

11
data/key.json Normal file
View File

@ -0,0 +1,11 @@
{
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
"priv_key": [
1,
"C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000"
],
"pub_key": [
1,
"619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
]
}

11
data/key2.json Normal file
View File

@ -0,0 +1,11 @@
{
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
"priv_key": [
1,
"34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000"
],
"pub_key": [
1,
"352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
]
}

View File

@ -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"
]
}

View File

@ -1,6 +1,6 @@
#! /bin/bash
killall -9 basecoin tendermint
killall -9 adam tendermint
TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all
TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all

View File

@ -1,10 +1,5 @@
{
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"

View File

@ -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"
]
}

View File

@ -1,10 +1,5 @@
{
"address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"

View File

@ -18,19 +18,19 @@ echo "CHAIN_ID1: $CHAIN_ID1"
echo "CHAIN_ID2: $CHAIN_ID2"
# make reusable chain flags
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json"
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657"
CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json"
CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657"
echo ""
echo "... starting chains"
echo ""
# start the first node
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
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 "... waiting for chains to start"
@ -40,20 +40,20 @@ sleep 10
echo "... registering chain1 on chain2"
echo ""
# 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 "... creating egress packet on chain1"
echo ""
# create a packet on chain1 destined for chain2
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 "... querying for packet data"
echo ""
# 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)
PACKET=$(echo $QUERY_RESULT | jq .value)
PROOF=$(echo $QUERY_RESULT | jq .proof)
@ -75,7 +75,7 @@ echo ""
echo "... querying for block data"
echo ""
# 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=$(removeQuotes $HEADER)
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
@ -89,16 +89,19 @@ echo ""
echo "... updating state of chain1 on chain2"
echo ""
# 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 "... posting packet from chain1 on chain2"
echo ""
# 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 "... checking if the packet is present on chain2"
echo ""
# 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!"

View File

@ -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)

View File

@ -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).

7
docs/guide/deployment.md Normal file
View File

@ -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???

View File

@ -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)

301
docs/guide/ibc.md Normal file
View File

@ -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!

13
docs/guide/install.md Normal file
View File

@ -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`.

View File

@ -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

View File

@ -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).

View File

@ -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))
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -32,9 +32,9 @@ func (cp *CounterPlugin) StateKey() []byte {
return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name))
}
func New(name string) *CounterPlugin {
func New() *CounterPlugin {
return &CounterPlugin{
name: name,
name: "counter",
}
}