Merge remote-tracking branch 'origin/master'

This commit is contained in:
Ethan Buchman 2017-02-13 15:57:29 -05:00
commit 82c662f6ef
55 changed files with 3915 additions and 230 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.swp
vendor
merkleeyes.db

View File

@ -3,8 +3,11 @@
all: test install
NOVENDOR = go list github.com/tendermint/basecoin/... | grep -v /vendor/
install:
build:
go build github.com/tendermint/basecoin/cmd/...
install:
go install github.com/tendermint/basecoin/cmd/...
test:
@ -20,4 +23,4 @@ update_deps:
get_vendor_deps:
go get github.com/Masterminds/glide
glide install

View File

@ -1,74 +1,57 @@
# Basecoin
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.
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.
## Contents
1. [Installation](#installation)
1. [(Advice for go novices)](./GoBasics.md)
1. [Using the plugin system](#plugins)
1. [Forking the codebase](#forking)
1. [Tutorials and other reading](#tutorials)
WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready.
## Installation
We use glide for dependency management. The prefered way of compiling from source is the following:
On a good day, basecoin can be installed like a normal Go program:
```
go get -u github.com/tendermint/basecoin/cmd/basecoin
```
In some cases, if that fails, or if another branch is required,
we use `glide` for dependency management.
The guaranteed correct way of compiling from source, assuming you've already
run `go get` or otherwise cloned the repo, is:
```
go get github.com/tendermint/basecoin
cd $GOPATH/src/github.com/tendermint/basecoin
git checkout develop # (until we release tendermint v0.9)
make get_vendor_deps
make install
```
This will create the `basecoin` binary.
## Plugins
Basecoin handles public-key authentication of transaction, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), sending currency (one-to-one or n-to-n 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, you can take advantage of the basecoin plugin system.
The Plugin interface is defined in `types/plugin.go`:
```
type Plugin interface {
Name() string
SetOption(store KVStore, key string, value string) (log string)
RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result)
InitChain(store KVStore, vals []*abci.Validator)
BeginBlock(store KVStore, height uint64)
EndBlock(store KVStore, height uint64) []*abci.Validator
}
```
`RunTx` is where you can handle any special transactions directed to your application. To see a very simple implementation, look at the demo [counter plugin](./plugins/counter/counter.go). If you want to create your own currency using a plugin, you don't have to fork basecoin at all. Just make your own repo, add the implementation of your custom plugin, and then build your own main script that instatiates BaseCoin and registers your plugin.
An example is worth a 1000 words, so please take a look [at this example](https://github.com/tendermint/basecoin/blob/abci_proof/cmd/paytovote/main.go#L25-L31), in a dev branch for now. You can use the same technique in your own repo.
There are a lot of changes on the dev branch, which should be merged in my early February, so experiment, but things will change soon....
## Forking
If you do want to fork basecoin, we would be happy if this was done in a public repo and any enhancements made as PRs on github. However, this is under the Apache license and you are free to keep the code private if you wish.
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 caling github.com/ORIGINAL/repo, which is very confusing. My prefered 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)
## Tutorials
We are working on some tutorials that will show you how to set up the genesis block, build a plugin to add custom logic, deploy to a tendermint testnet, and connect a UI to your blockchain. They should be published during the course of February 2017, so stay tuned....
This will create the `basecoin` binary in `$GOPATH/bin`.
## Command Line Interface
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 tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100`
See `basecoin --help` and `basecoin [cmd] --help` for more details`.
## Learn more
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.

View File

@ -36,7 +36,12 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
}
}
// TMSP::Info
// For testing, not thread safe!
func (app *Basecoin) GetState() *sm.State {
return app.state.CacheWrap()
}
// ABCI::Info
func (app *Basecoin) Info() abci.ResponseInfo {
return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version)}
}
@ -45,7 +50,7 @@ func (app *Basecoin) RegisterPlugin(plugin types.Plugin) {
app.plugins.RegisterPlugin(plugin)
}
// TMSP::SetOption
// ABCI::SetOption
func (app *Basecoin) SetOption(key string, value string) (log string) {
PluginName, key := splitKey(key)
if PluginName != PluginNameBase {
@ -75,7 +80,7 @@ func (app *Basecoin) SetOption(key string, value string) (log string) {
}
}
// TMSP::DeliverTx
// ABCI::DeliverTx
func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) {
if len(txBytes) > maxTxSize {
return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum")
@ -93,10 +98,10 @@ func (app *Basecoin) DeliverTx(txBytes []byte) (res abci.Result) {
if res.IsErr() {
return res.PrependLog("Error in DeliverTx")
}
return abci.OK
return res
}
// TMSP::CheckTx
// ABCI::CheckTx
func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) {
if len(txBytes) > maxTxSize {
return abci.ErrBaseEncodingError.AppendLog("Tx size exceeds maximum")
@ -117,16 +122,24 @@ func (app *Basecoin) CheckTx(txBytes []byte) (res abci.Result) {
return abci.OK
}
// TMSP::Query
func (app *Basecoin) Query(query []byte) (res abci.Result) {
if len(query) == 0 {
return abci.ErrEncodingError.SetLog("Query cannot be zero length")
// ABCI::Query
func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQuery) {
if len(reqQuery.Data) == 0 {
resQuery.Log = "Query cannot be zero length"
resQuery.Code = abci.CodeType_EncodingError
return
}
return app.eyesCli.QuerySync(query)
resQuery, err := app.eyesCli.QuerySync(reqQuery)
if err != nil {
resQuery.Log = "Failed to query MerkleEyes: " + err.Error()
resQuery.Code = abci.CodeType_InternalError
return
}
return
}
// TMSP::Commit
// ABCI::Commit
func (app *Basecoin) Commit() (res abci.Result) {
// Commit state
@ -141,25 +154,25 @@ func (app *Basecoin) Commit() (res abci.Result) {
return res
}
// TMSP::InitChain
// ABCI::InitChain
func (app *Basecoin) InitChain(validators []*abci.Validator) {
for _, plugin := range app.plugins.GetList() {
plugin.InitChain(app.state, validators)
}
}
// TMSP::BeginBlock
func (app *Basecoin) BeginBlock(height uint64) {
// ABCI::BeginBlock
func (app *Basecoin) BeginBlock(hash []byte, header *abci.Header) {
for _, plugin := range app.plugins.GetList() {
plugin.BeginBlock(app.state, height)
plugin.BeginBlock(app.state, hash, header)
}
}
// TMSP::EndBlock
func (app *Basecoin) EndBlock(height uint64) (diffs []*abci.Validator) {
// ABCI::EndBlock
func (app *Basecoin) EndBlock(height uint64) (res abci.ResponseEndBlock) {
for _, plugin := range app.plugins.GetList() {
moreDiffs := plugin.EndBlock(app.state, height)
diffs = append(diffs, moreDiffs...)
pluginRes := plugin.EndBlock(app.state, height)
res.Diffs = append(res.Diffs, pluginRes.Diffs...)
}
return
}
@ -175,3 +188,9 @@ func splitKey(key string) (prefix string, suffix string) {
}
return key, ""
}
// (not meant to be called)
// assert that Basecoin implements `abci.BlockchainAware` at compile-time
func _assertABCIBlockchainAware(basecoin *Basecoin) abci.BlockchainAware {
return basecoin
}

63
app/genesis.go Normal file
View File

@ -0,0 +1,63 @@
package app
import (
"encoding/json"
"fmt"
"reflect"
"github.com/pkg/errors"
cmn "github.com/tendermint/go-common"
)
func (app *Basecoin) LoadGenesis(path string) error {
kvz, err := loadGenesis(path)
if err != nil {
return err
}
for _, kv := range kvz {
log := app.SetOption(kv.Key, kv.Value)
// TODO: remove debug output
fmt.Printf("Set %v=%v. Log: %v", kv.Key, kv.Value, log)
}
return nil
}
type keyValue struct {
Key string `json:"key"`
Value string `json:"value"`
}
func loadGenesis(filePath string) (kvz []keyValue, err error) {
kvz_ := []interface{}{}
bytes, err := cmn.ReadFile(filePath)
if err != nil {
return nil, errors.Wrap(err, "loading genesis file")
}
err = json.Unmarshal(bytes, &kvz_)
if err != nil {
return nil, errors.Wrap(err, "parsing genesis file")
}
if len(kvz_)%2 != 0 {
return nil, errors.New("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
}
for i := 0; i < len(kvz_); i += 2 {
keyIfc := kvz_[i]
valueIfc := kvz_[i+1]
var key, value string
key, ok := keyIfc.(string)
if !ok {
return nil, errors.Errorf("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc))
}
if value_, ok := valueIfc.(string); ok {
value = value_
} else {
valueBytes, err := json.Marshal(valueIfc)
if err != nil {
return nil, errors.Errorf("genesis had invalid value %v: %v", value_, err.Error())
}
value = string(valueBytes)
}
kvz = append(kvz, keyValue{key, value})
}
return kvz, nil
}

26
circle.yml Normal file
View File

@ -0,0 +1,26 @@
machine:
environment:
GOPATH: /home/ubuntu/.go_workspace
REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
hosts:
circlehost: 127.0.0.1
localhost: 127.0.0.1
checkout:
post:
- rm -rf $REPO
- mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME
- mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO
dependencies:
override:
- go get github.com/Masterminds/glide
- go version
- glide --version
- "cd $REPO && glide install"
test:
override:
- "cd $REPO && make test"

View File

@ -1,94 +1,25 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"reflect"
"os"
"github.com/tendermint/abci/server"
"github.com/tendermint/basecoin/app"
. "github.com/tendermint/go-common"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/urfave/cli"
)
func main() {
addrPtr := flag.String("address", "tcp://0.0.0.0:46658", "Listen address")
eyesPtr := flag.String("eyes", "local", "MerkleEyes address, or 'local' for embedded")
genFilePath := flag.String("genesis", "", "Genesis file, if any")
flag.Parse()
// Connect to MerkleEyes
eyesCli, err := eyes.NewClient(*eyesPtr, "socket")
if err != nil {
Exit("connect to MerkleEyes: " + err.Error())
app := cli.NewApp()
app.Name = "basecoin"
app.Usage = "basecoin [command] [args...]"
app.Version = "0.1.0"
app.Commands = []cli.Command{
commands.StartCmd,
commands.TxCmd,
commands.QueryCmd,
commands.KeyCmd,
commands.VerifyCmd, // TODO: move to merkleeyes?
commands.BlockCmd, // TODO: move to adam?
commands.AccountCmd,
}
// Create Basecoin app
app := app.NewBasecoin(eyesCli)
// If genesis file was specified, set key-value options
if *genFilePath != "" {
kvz := loadGenesis(*genFilePath)
for _, kv := range kvz {
log := app.SetOption(kv.Key, kv.Value)
fmt.Println(Fmt("Set %v=%v. Log: %v", kv.Key, kv.Value, log))
}
}
// Start the listener
svr, err := server.NewServer(*addrPtr, "socket", app)
if err != nil {
Exit("create listener: " + err.Error())
}
// Wait forever
TrapSignal(func() {
// Cleanup
svr.Stop()
})
}
//----------------------------------------
type KeyValue struct {
Key string `json:"key"`
Value string `json:"value"`
}
func loadGenesis(filePath string) (kvz []KeyValue) {
kvz_ := []interface{}{}
bytes, err := ReadFile(filePath)
if err != nil {
Exit("loading genesis file: " + err.Error())
}
err = json.Unmarshal(bytes, &kvz_)
if err != nil {
Exit("parsing genesis file: " + err.Error())
}
if len(kvz_)%2 != 0 {
Exit("genesis cannot have an odd number of items. Format = [key1, value1, key2, value2, ...]")
}
for i := 0; i < len(kvz_); i += 2 {
keyIfc := kvz_[i]
valueIfc := kvz_[i+1]
var key, value string
key, ok := keyIfc.(string)
if !ok {
Exit(Fmt("genesis had invalid key %v of type %v", keyIfc, reflect.TypeOf(keyIfc)))
}
if value_, ok := valueIfc.(string); ok {
value = value_
} else {
valueBytes, err := json.Marshal(valueIfc)
if err != nil {
Exit(Fmt("genesis had invalid value %v: %v", value_, err.Error()))
}
value = string(valueBytes)
}
kvz = append(kvz, KeyValue{key, value})
}
return kvz
app.Run(os.Args)
}

131
cmd/commands/flags.go Normal file
View File

@ -0,0 +1,131 @@
package commands
import (
"github.com/urfave/cli"
)
// start flags
var (
AddrFlag = cli.StringFlag{
Name: "address",
Value: "tcp://0.0.0.0:46658",
Usage: "Listen address",
}
EyesFlag = cli.StringFlag{
Name: "eyes",
Value: "local",
Usage: "MerkleEyes address, or 'local' for embedded",
}
// TODO: move to config file
// eyesCacheSizePtr := flag.Int("eyes-cache-size", 10000, "MerkleEyes db cache size, for embedded")
DirFlag = cli.StringFlag{
Name: "dir",
Value: ".",
Usage: "Root directory",
}
InProcTMFlag = cli.BoolFlag{
Name: "in-proc",
Usage: "Run Tendermint in-process with the App",
}
)
// tx flags
var (
NodeFlag = cli.StringFlag{
Name: "node",
Value: "tcp://localhost:46657",
Usage: "Tendermint RPC address",
}
ToFlag = cli.StringFlag{
Name: "to",
Value: "",
Usage: "Destination address for the transaction",
}
AmountFlag = cli.IntFlag{
Name: "amount",
Value: 0,
Usage: "Amount of coins to send in the transaction",
}
FromFlag = cli.StringFlag{
Name: "from",
Value: "key.json",
Usage: "Path to a private key to sign the transaction",
}
SeqFlag = cli.IntFlag{
Name: "sequence",
Value: 0,
Usage: "Sequence number for the account",
}
CoinFlag = cli.StringFlag{
Name: "coin",
Value: "mycoin",
Usage: "Specify a coin denomination",
}
GasFlag = cli.IntFlag{
Name: "gas",
Value: 0,
Usage: "The amount of gas for the transaction",
}
FeeFlag = cli.IntFlag{
Name: "fee",
Value: 0,
Usage: "The transaction fee",
}
DataFlag = cli.StringFlag{
Name: "data",
Value: "",
Usage: "Data to send with the transaction",
}
NameFlag = cli.StringFlag{
Name: "name",
Value: "",
Usage: "Plugin to send the transaction to",
}
ChainIDFlag = cli.StringFlag{
Name: "chain_id",
Value: "test_chain_id",
Usage: "ID of the chain for replay protection",
}
)
// proof flags
var (
ProofFlag = cli.StringFlag{
Name: "proof",
Usage: "hex-encoded IAVL proof",
Value: "",
}
KeyFlag = cli.StringFlag{
Name: "key",
Usage: "key to the IAVL tree",
Value: "",
}
ValueFlag = cli.StringFlag{
Name: "value",
Usage: "value in the IAVL tree",
Value: "",
}
RootFlag = cli.StringFlag{
Name: "root",
Usage: "root hash of the IAVL tree",
Value: "",
}
)

324
cmd/commands/ibc.go Normal file
View File

@ -0,0 +1,324 @@
package commands
import (
"encoding/hex"
"errors"
"fmt"
"io/ioutil"
"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"
"github.com/tendermint/go-wire"
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: TxFlags,
Subcommands: []cli.Command{
IbcRegisterTxCmd,
IbcUpdateTxCmd,
IbcPacketTxCmd,
},
}
IbcRegisterTxCmd = cli.Command{
Name: "register",
Usage: "Register a blockchain via IBC",
Action: func(c *cli.Context) error {
return cmdIBCRegisterTx(c)
},
Flags: []cli.Flag{
IbcChainIDFlag,
IbcGenesisFlag,
},
}
IbcUpdateTxCmd = cli.Command{
Name: "update",
Usage: "Update the latest state of a blockchain via IBC",
Action: func(c *cli.Context) error {
return cmdIBCUpdateTx(c)
},
Flags: []cli.Flag{
IbcHeaderFlag,
IbcCommitFlag,
},
}
IbcPacketTxCmd = cli.Command{
Name: "packet",
Usage: "Send a new packet via IBC",
Subcommands: []cli.Command{
IbcPacketCreateTx,
IbcPacketPostTx,
},
}
IbcPacketCreateTx = cli.Command{
Name: "create",
Usage: "Create an egress IBC packet",
Action: func(c *cli.Context) error {
return cmdIBCPacketCreateTx(c)
},
Flags: []cli.Flag{
IbcFromFlag,
IbcToFlag,
IbcTypeFlag,
IbcPayloadFlag,
IbcSequenceFlag,
},
}
IbcPacketPostTx = cli.Command{
Name: "post",
Usage: "Deliver an IBC packet to another chain",
Action: func(c *cli.Context) error {
return cmdIBCPacketPostTx(c)
},
Flags: []cli.Flag{
IbcFromFlag,
IbcHeightFlag,
IbcPacketFlag,
IbcProofFlag,
},
}
)
//---------------------------------------------------------------------
// ibc command implementations
func cmdIBCRegisterTx(c *cli.Context) error {
chainID := c.String("chain_id")
genesisFile := c.String("genesis")
parent := c.Parent()
genesisBytes, err := ioutil.ReadFile(genesisFile)
if err != nil {
return errors.New(cmn.Fmt("Error reading genesis file %v: %v", genesisFile, err))
}
ibcTx := ibc.IBCRegisterChainTx{
ibc.BlockchainGenesis{
ChainID: chainID,
Genesis: string(genesisBytes),
},
}
fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx)))
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
name := "IBC"
return AppTx(parent, name, data)
}
func cmdIBCUpdateTx(c *cli.Context) error {
headerBytes, err := hex.DecodeString(StripHex(c.String("header")))
if err != nil {
return errors.New(cmn.Fmt("Header (%v) is invalid hex: %v", c.String("header"), err))
}
commitBytes, err := hex.DecodeString(StripHex(c.String("commit")))
if err != nil {
return errors.New(cmn.Fmt("Commit (%v) is invalid hex: %v", c.String("commit"), err))
}
header := new(tmtypes.Header)
commit := new(tmtypes.Commit)
if err := wire.ReadBinaryBytes(headerBytes, &header); err != nil {
return errors.New(cmn.Fmt("Error unmarshalling header: %v", err))
}
if err := wire.ReadBinaryBytes(commitBytes, &commit); err != nil {
return errors.New(cmn.Fmt("Error unmarshalling commit: %v", err))
}
ibcTx := ibc.IBCUpdateChainTx{
Header: *header,
Commit: *commit,
}
fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx)))
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
name := "IBC"
return AppTx(c.Parent(), name, data)
}
func cmdIBCPacketCreateTx(c *cli.Context) error {
fromChain, toChain := c.String("from"), c.String("to")
packetType := c.String("type")
payloadBytes, err := hex.DecodeString(StripHex(c.String("payload")))
if err != nil {
return errors.New(cmn.Fmt("Payload (%v) is invalid hex: %v", c.String("payload"), err))
}
sequence, err := getIBCSequence(c)
if err != nil {
return err
}
ibcTx := ibc.IBCPacketCreateTx{
Packet: ibc.Packet{
SrcChainID: fromChain,
DstChainID: toChain,
Sequence: sequence,
Type: packetType,
Payload: payloadBytes,
},
}
fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx)))
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
return AppTx(c.Parent().Parent(), "IBC", data)
}
func cmdIBCPacketPostTx(c *cli.Context) error {
fromChain, fromHeight := c.String("from"), c.Int("height")
packetBytes, err := hex.DecodeString(StripHex(c.String("packet")))
if err != nil {
return errors.New(cmn.Fmt("Packet (%v) is invalid hex: %v", c.String("packet"), err))
}
proofBytes, err := hex.DecodeString(StripHex(c.String("proof")))
if err != nil {
return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err))
}
var packet ibc.Packet
proof := new(merkle.IAVLProof)
if err := wire.ReadBinaryBytes(packetBytes, &packet); err != nil {
return errors.New(cmn.Fmt("Error unmarshalling packet: %v", err))
}
if err := wire.ReadBinaryBytes(proofBytes, &proof); err != nil {
return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err))
}
ibcTx := ibc.IBCPacketPostTx{
FromChainID: fromChain,
FromChainHeight: uint64(fromHeight),
Packet: packet,
Proof: proof,
}
fmt.Println("IBCTx:", string(wire.JSONBytes(ibcTx)))
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
return AppTx(c.Parent().Parent(), "IBC", data)
}
func getIBCSequence(c *cli.Context) (uint64, error) {
if c.IsSet("sequence") {
return uint64(c.Int("sequence")), nil
}
// TODO: get sequence
return 0, nil
}

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
}

213
cmd/commands/query.go Normal file
View File

@ -0,0 +1,213 @@
package commands
import (
"encoding/hex"
"errors"
"fmt"
"strconv"
"github.com/urfave/cli"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire"
tmtypes "github.com/tendermint/tendermint/types"
)
var (
QueryCmd = cli.Command{
Name: "query",
Usage: "Query the merkle tree",
ArgsUsage: "<key>",
Action: func(c *cli.Context) error {
return cmdQuery(c)
},
Flags: []cli.Flag{
NodeFlag,
},
}
AccountCmd = cli.Command{
Name: "account",
Usage: "Get details of an account",
ArgsUsage: "<address>",
Action: func(c *cli.Context) error {
return cmdAccount(c)
},
Flags: []cli.Flag{
NodeFlag,
},
}
BlockCmd = cli.Command{
Name: "block",
Usage: "Get the header and commit of a block",
ArgsUsage: "<height>",
Action: func(c *cli.Context) error {
return cmdBlock(c)
},
Flags: []cli.Flag{
NodeFlag,
},
}
VerifyCmd = cli.Command{
Name: "verify",
Usage: "Verify the IAVL proof",
Action: func(c *cli.Context) error {
return cmdVerify(c)
},
Flags: []cli.Flag{
ProofFlag,
KeyFlag,
ValueFlag,
RootFlag,
},
}
)
func cmdQuery(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("query command requires an argument ([key])")
}
keyString := c.Args()[0]
key := []byte(keyString)
if isHex(keyString) {
// convert key to bytes
var err error
key, err = hex.DecodeString(StripHex(keyString))
if err != nil {
return errors.New(cmn.Fmt("Query key (%v) is invalid hex: %v", keyString, err))
}
}
resp, err := Query(c.String("node"), key)
if err != nil {
return err
}
if !resp.Code.IsOK() {
return errors.New(cmn.Fmt("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log))
}
val := resp.Value
proof := resp.Proof
height := resp.Height
fmt.Println(string(wire.JSONBytes(struct {
Value []byte `json:"value"`
Proof []byte `json:"proof"`
Height uint64 `json:"height"`
}{val, proof, height})))
return nil
}
func cmdAccount(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("account command requires an argument ([address])")
}
addrHex := StripHex(c.Args()[0])
// convert destination address to bytes
addr, err := hex.DecodeString(addrHex)
if err != nil {
return errors.New(cmn.Fmt("Account address (%v) is invalid hex: %v", addrHex, err))
}
acc, err := getAcc(c.String("node"), addr)
if err != nil {
return err
}
fmt.Println(string(wire.JSONBytes(acc)))
return nil
}
func cmdBlock(c *cli.Context) error {
if len(c.Args()) != 1 {
return errors.New("block command requires an argument ([height])")
}
heightString := c.Args()[0]
height, err := strconv.Atoi(heightString)
if err != nil {
return errors.New(cmn.Fmt("Height must be an int, got %v: %v", heightString, err))
}
/*block, err := getBlock(c, height)
if err != nil {
return err
}*/
nextBlock, err := getBlock(c, height+1)
if err != nil {
return err
}
fmt.Println(string(wire.JSONBytes(struct {
Hex BlockHex `json:"hex"`
JSON BlockJSON `json:"json"`
}{
BlockHex{
Header: wire.BinaryBytes(nextBlock.Header),
Commit: wire.BinaryBytes(nextBlock.LastCommit),
},
BlockJSON{
Header: nextBlock.Header,
Commit: nextBlock.LastCommit,
},
})))
return nil
}
type BlockHex struct {
Header []byte `json:"header"`
Commit []byte `json:"commit"`
}
type BlockJSON struct {
Header *tmtypes.Header `json:"header"`
Commit *tmtypes.Commit `json:"commit"`
}
func cmdVerify(c *cli.Context) error {
keyString, valueString := c.String("key"), c.String("value")
var err error
key := []byte(keyString)
if isHex(keyString) {
key, err = hex.DecodeString(StripHex(keyString))
if err != nil {
return errors.New(cmn.Fmt("Key (%v) is invalid hex: %v", keyString, err))
}
}
value := []byte(valueString)
if isHex(valueString) {
value, err = hex.DecodeString(StripHex(valueString))
if err != nil {
return errors.New(cmn.Fmt("Value (%v) is invalid hex: %v", valueString, err))
}
}
root, err := hex.DecodeString(StripHex(c.String("root")))
if err != nil {
return errors.New(cmn.Fmt("Root (%v) is invalid hex: %v", c.String("root"), err))
}
proofBytes, err := hex.DecodeString(StripHex(c.String("proof")))
if err != nil {
return errors.New(cmn.Fmt("Proof (%v) is invalid hex: %v", c.String("proof"), err))
}
proof, err := merkle.ReadProof(proofBytes)
if err != nil {
return errors.New(cmn.Fmt("Error unmarshalling proof: %v", err))
}
if proof.Verify(key, value, root) {
fmt.Println("OK")
} else {
return errors.New("Proof does not verify")
}
return nil
}

131
cmd/commands/start.go Normal file
View File

@ -0,0 +1,131 @@
package commands
import (
"errors"
"os"
"path"
"github.com/urfave/cli"
"github.com/tendermint/abci/server"
cmn "github.com/tendermint/go-common"
cfg "github.com/tendermint/go-config"
//logger "github.com/tendermint/go-logger"
eyes "github.com/tendermint/merkleeyes/client"
tmcfg "github.com/tendermint/tendermint/config/tendermint"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/types"
)
var config cfg.Config
const EyesCacheSize = 10000
var StartCmd = cli.Command{
Name: "start",
Usage: "Start basecoin",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdStart(c)
},
Flags: []cli.Flag{
AddrFlag,
EyesFlag,
DirFlag,
InProcTMFlag,
ChainIDFlag,
},
}
type plugin struct {
name string
newPlugin func() types.Plugin
}
var plugins = []plugin{}
// 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 {
// Connect to MerkleEyes
var eyesCli *eyes.Client
if c.String("eyes") == "local" {
eyesCli = eyes.NewLocalClient(path.Join(c.String("dir"), "merkleeyes.db"), EyesCacheSize)
} else {
var err error
eyesCli, err = eyes.NewClient(c.String("eyes"))
if err != nil {
return errors.New("connect to MerkleEyes: " + err.Error())
}
}
// Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli)
// register all plugins
for _, p := range plugins {
basecoinApp.RegisterPlugin(p.newPlugin())
}
// If genesis file exists, set key-value options
genesisFile := path.Join(c.String("dir"), "genesis.json")
if _, err := os.Stat(genesisFile); err == nil {
err := basecoinApp.LoadGenesis(genesisFile)
if err != nil {
return errors.New(cmn.Fmt("%+v", err))
}
}
if c.Bool("in-proc") {
startTendermint(c, basecoinApp)
} else {
startBasecoinABCI(c, basecoinApp)
}
return nil
}
func startBasecoinABCI(c *cli.Context, basecoinApp *app.Basecoin) error {
// Start the ABCI listener
svr, err := server.NewServer(c.String("address"), "socket", basecoinApp)
if err != nil {
return errors.New("create listener: " + err.Error())
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
svr.Stop()
})
return nil
}
func startTendermint(c *cli.Context, basecoinApp *app.Basecoin) {
// Get configuration
config = tmcfg.GetConfig("")
// logger.SetLogLevel("notice") //config.GetString("log_level"))
// parseFlags(config, args[1:]) // Command line overrides
// Create & start tendermint node
privValidatorFile := config.GetString("priv_validator_file")
privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile)
n := node.NewNode(config, privValidator, proxy.NewLocalClientCreator(basecoinApp))
n.Start()
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
n.Stop()
})
}

219
cmd/commands/tx.go Normal file
View File

@ -0,0 +1,219 @@
package commands
import (
"encoding/hex"
"errors"
"fmt"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
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: "send",
Usage: "Create, sign, and broadcast a SendTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdSendTx(c)
},
Flags: append(TxFlags, ToFlag),
}
AppTxCmd = cli.Command{
Name: "app",
Usage: "Create, sign, and broadcast a raw AppTx transaction",
ArgsUsage: "",
Action: func(c *cli.Context) error {
return cmdAppTx(c)
},
Flags: append(TxFlags, NameFlag, DataFlag),
// Subcommands are dynamically registered with plugins as needed
Subcommands: []cli.Command{},
}
)
// 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 {
toHex := c.String("to")
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
chainID := c.String("chain_id")
// convert destination address to bytes
to, err := hex.DecodeString(StripHex(toHex))
if err != nil {
return errors.New("To address is invalid hex: " + err.Error())
}
// load the priv key
privKey := LoadKey(fromFile)
// get the sequence number for the tx
sequence, err := getSeq(c, privKey.Address)
if err != nil {
return err
}
// craft the tx
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
output := newOutput(to, coin, amount)
tx := &types.SendTx{
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Inputs: []types.TxInput{input},
Outputs: []types.TxOutput{output},
}
// sign that puppy
signBytes := tx.SignBytes(chainID)
tx.Inputs[0].Signature = privKey.Sign(signBytes)
fmt.Println("Signed SendTx:")
fmt.Println(string(wire.JSONBytes(tx)))
// broadcast the transaction to tendermint
if _, err := broadcastTx(c, tx); err != nil {
return err
}
return nil
}
func cmdAppTx(c *cli.Context) error {
// convert data to bytes
dataString := c.String("data")
data := []byte(dataString)
if isHex(dataString) {
data, _ = hex.DecodeString(dataString)
}
name := c.String("name")
return AppTx(c, name, data)
}
func AppTx(c *cli.Context, name string, data []byte) error {
fromFile := c.String("from")
amount := int64(c.Int("amount"))
coin := c.String("coin")
gas, fee := c.Int("gas"), int64(c.Int("fee"))
chainID := c.String("chain_id")
privKey := tmtypes.LoadPrivValidator(fromFile)
sequence, err := getSeq(c, privKey.Address)
if err != nil {
return err
}
input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence)
tx := &types.AppTx{
Gas: int64(gas),
Fee: types.Coin{coin, fee},
Name: name,
Input: input,
Data: data,
}
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID))
fmt.Println("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx)))
res, err := broadcastTx(c, tx)
if err != nil {
return err
}
fmt.Printf("Response: %X\n", res)
return nil
}
// broadcast the transaction to tendermint
func broadcastTx(c *cli.Context, tx types.Tx) ([]byte, error) {
tmResult := new(ctypes.TMResult)
tmAddr := c.String("node")
clientURI := client.NewClientURI(tmAddr)
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := []byte(wire.BinaryBytes(struct {
types.Tx `json:"unwrap"`
}{tx}))
_, err := clientURI.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
}
res := (*tmResult).(*ctypes.ResultBroadcastTxCommit)
// if it fails check, we don't even get a delivertx back!
if !res.CheckTx.Code.IsOK() {
r := res.CheckTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
}
if !res.DeliverTx.Code.IsOK() {
r := res.DeliverTx
return nil, errors.New(cmn.Fmt("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log))
}
return res.DeliverTx.Data, nil
}
// if the sequence flag is set, return it;
// else, fetch the account by querying the app and return the sequence number
func getSeq(c *cli.Context, address []byte) (int, error) {
if c.IsSet("sequence") {
return c.Int("sequence"), nil
}
tmAddr := c.String("node")
acc, err := getAcc(tmAddr, address)
if err != nil {
return 0, err
}
return acc.Sequence + 1, nil
}
func newOutput(to []byte, coin string, amount int64) types.TxOutput {
return types.TxOutput{
Address: to,
Coins: types.Coins{
types.Coin{
Denom: coin,
Amount: amount,
},
},
}
}

94
cmd/commands/utils.go Normal file
View File

@ -0,0 +1,94 @@
package commands
import (
"encoding/hex"
"errors"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/types"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types"
)
// Returns true for non-empty hex-string prefixed with "0x"
func isHex(s string) bool {
if len(s) > 2 && s[:2] == "0x" {
_, err := hex.DecodeString(s[2:])
if err != nil {
return false
}
return true
}
return false
}
func StripHex(s string) string {
if isHex(s) {
return s[2:]
}
return s
}
func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) {
clientURI := client.NewClientURI(tmAddr)
tmResult := new(ctypes.TMResult)
params := map[string]interface{}{
"path": "/key",
"data": key,
"prove": true,
}
_, err := clientURI.Call("abci_query", params, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error calling /abci_query: %v", err))
}
res := (*tmResult).(*ctypes.ResultABCIQuery)
if !res.Response.Code.IsOK() {
return nil, errors.New(cmn.Fmt("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log))
}
return &res.Response, nil
}
// fetch the account by querying the app
func getAcc(tmAddr string, address []byte) (*types.Account, error) {
key := append([]byte("base/a/"), address...)
response, err := Query(tmAddr, key)
if err != nil {
return nil, err
}
accountBytes := response.Value
if len(accountBytes) == 0 {
return nil, errors.New(cmn.Fmt("Account bytes are empty for address: %X ", address))
}
var acc *types.Account
err = wire.ReadBinaryBytes(accountBytes, &acc)
if err != nil {
return nil, errors.New(cmn.Fmt("Error reading account %X error: %v",
accountBytes, err.Error()))
}
return acc, nil
}
func getBlock(c *cli.Context, height int) (*tmtypes.Block, error) {
tmResult := new(ctypes.TMResult)
tmAddr := c.String("node")
clientURI := client.NewClientURI(tmAddr)
_, err := clientURI.Call("block", map[string]interface{}{"height": height}, tmResult)
if err != nil {
return nil, errors.New(cmn.Fmt("Error on broadcast tx: %v", err))
}
res := (*tmResult).(*ctypes.ResultBlock)
return res.Block, nil
}

54
cmd/counter/cmd.go Normal file
View File

@ -0,0 +1,54 @@
package main
import (
"fmt"
wire "github.com/tendermint/go-wire"
"github.com/urfave/cli"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/basecoin/plugins/counter"
"github.com/tendermint/basecoin/types"
)
func init() {
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")
counterTx := counter.CounterTx{
Valid: valid,
Fee: types.Coins{
{
Denom: c.String("coin"),
Amount: int64(c.Int("fee")),
},
},
}
fmt.Println("CounterTx:", string(wire.JSONBytes(counterTx)))
data := wire.BinaryBytes(counterTx)
name := "counter"
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)
}

12
data/genesis.json Normal file
View File

@ -0,0 +1,12 @@
[
"base/chainID", "test_chain_id",
"base/account", {
"pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"],
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

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

13
demo/clean.sh Normal file
View File

@ -0,0 +1,13 @@
#! /bin/bash
killall -9 basecoin tendermint
TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all
TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all
rm -rf ./data/chain1/basecoin/merkleeyes.db
rm -rf ./data/chain2/basecoin/merkleeyes.db
rm ./*.log
rm ./data/chain1/tendermint/*.bak
rm ./data/chain2/tendermint/*.bak

View File

@ -0,0 +1,12 @@
[
"base/chainID", "test_chain_1",
"base/account", {
"pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"],
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

View File

@ -0,0 +1,12 @@
{
"address": "D397BC62B435F3CF50570FBAB4340FE52C60858F",
"priv_key": [
1,
"39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
],
"pub_key": [
1,
"B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
]
}

View File

@ -0,0 +1,11 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
proxy_app = "tcp://127.0.0.1:46658"
moniker = "anonymous"
node_laddr = "tcp://0.0.0.0:46656"
seeds = ""
fast_sync = true
db_backend = "leveldb"
log_level = "notice"
rpc_laddr = "tcp://0.0.0.0:46657"

View File

@ -0,0 +1,15 @@
{
"app_hash": "",
"chain_id": "test_chain_1",
"genesis_time": "0001-01-01T00:00:00.000Z",
"validators": [
{
"amount": 10,
"name": "",
"pub_key": [
1,
"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
]
}
]
}

View File

@ -0,0 +1,16 @@
{
"address": "EBB0B4A899973C524A6BB18A161056A55F590F41",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"5FFDC1EA5FA2CA4A0A5503C86D2D348C5B401AD80FAA1899508F1ED00D8982E8D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
],
"pub_key": [
1,
"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
]
}

View File

@ -0,0 +1,12 @@
[
"base/chainID", "test_chain_2",
"base/account", {
"pub_key": [1, "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"],
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]

View File

@ -0,0 +1,11 @@
{
"address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47",
"priv_key": [
1,
"22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
],
"pub_key": [
1,
"0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D"
]
}

View File

@ -0,0 +1,11 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
proxy_app = "tcp://127.0.0.1:46658"
moniker = "anonymous"
node_laddr = "tcp://0.0.0.0:46656"
seeds = ""
fast_sync = true
db_backend = "leveldb"
log_level = "notice"
rpc_laddr = "tcp://0.0.0.0:46657"

View File

@ -0,0 +1,15 @@
{
"app_hash": "",
"chain_id": "test_chain_2",
"genesis_time": "0001-01-01T00:00:00.000Z",
"validators": [
{
"amount": 10,
"name": "",
"pub_key": [
1,
"9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
]
}
]
}

View File

@ -0,0 +1,16 @@
{
"address": "D42CFCB9C42DF9A73143EEA89255D1DF027B6240",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"6353FAF4ADEB03EA496A9EAE5BE56C4C6A851CB705401788184FDC9198413C2C9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
],
"pub_key": [
1,
"9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
]
}

107
demo/start.sh Normal file
View File

@ -0,0 +1,107 @@
#! /bin/bash
set -eu
cd $GOPATH/src/github.com/tendermint/basecoin/demo
function removeQuotes() {
temp="${1%\"}"
temp="${temp#\"}"
echo "$temp"
}
# grab the chain ids
CHAIN_ID1=$(cat ./data/chain1/basecoin/genesis.json | jq .[1])
CHAIN_ID1=$(removeQuotes $CHAIN_ID1)
CHAIN_ID2=$(cat ./data/chain2/basecoin/genesis.json | jq .[1])
CHAIN_ID2=$(removeQuotes $CHAIN_ID2)
echo "CHAIN_ID1: $CHAIN_ID1"
echo "CHAIN_ID2: $CHAIN_ID2"
# make reusable chain flags
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 --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 --dir ./data/chain2/basecoin &> chain2_basecoin.log &
echo ""
echo "... waiting for chains to start"
echo ""
sleep 10
echo "... registering chain1 on chain2"
echo ""
# register chain1 on chain2
basecoin 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 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)
HEIGHT=$(echo $QUERY_RESULT | jq .height)
PACKET=$(echo $QUERY_RESULT | jq .value)
PROOF=$(echo $QUERY_RESULT | jq .proof)
PACKET=$(removeQuotes $PACKET)
PROOF=$(removeQuotes $PROOF)
echo ""
echo "QUERY_RESULT: $QUERY_RESULT"
echo "HEIGHT: $HEIGHT"
echo "PACKET: $PACKET"
echo "PROOF: $PROOF"
echo ""
echo "... waiting for some blocks to be mined"
echo ""
sleep 5
echo ""
echo "... querying for block data"
echo ""
# get the header and commit for the height
HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
HEADER=$(removeQuotes $HEADER)
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
COMMIT=$(removeQuotes $COMMIT)
echo ""
echo "HEADER_AND_COMMIT: $HEADER_AND_COMMIT"
echo "HEADER: $HEADER"
echo "COMMIT: $COMMIT"
echo ""
echo "... updating state of chain1 on chain2"
echo ""
# update the state of chain1 on chain2
basecoin 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 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
echo ""
echo "DONE!"

View File

@ -0,0 +1,133 @@
# 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).
**Note** All code is on the 0.9 pre-release branch, you may have to [install tendermint from source](https://tendermint.com/docs/guides/install) until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions)
## 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,89 @@
# 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.
In tendermint, the `Fee` is meant to be used by the validators to inform the ordering of transactions, like in bitcoin. And the `Gas` is meant to be used by the application plugin to control its execution. There is currently no means to pass `Fee` information to the tendermint validators, but it will come soon...
Second, notice that the `PubKey` only needs to be sent for `Sequence == 0`.
After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it,
using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions
as it uses a different elliptic curve scheme which enables the public key to be 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. When using multiple inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction.
## Plugins
Basecoin actually defines a second transaction type, the `AppTx`,
which enables the functionality to be extended via custom plugins.
To learn more about the `AppTx` and plugin system, see the [plugin design document](plugin-design.md).
To implement your first plugin, see [plugin tutorial](example-plugin.md).

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,421 @@
# 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: "mycoin")
--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 methods.
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`. Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0. If your app needs more initialization than empty variables, then do this logic here in an `else` block.
Finally, we can update the state's `Counter`, and save the state back to the store:
```
//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 ("mycoin") 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)

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

@ -0,0 +1,300 @@
# 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 the `AppHash` from height H, we need the signatures from `LastCommit` at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1)
Unlike Proof-of-Work, the light-client protocol does not need to download and
check all the headers in the blockchain - the client can always jump straight
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
[basecoin](/docs/guide/install.md).
`basecoin` is a framework for creating new cryptocurrency applications.
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 &
basecoin start --dir ./data/chain1/basecoin &> chain1_basecoin.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 &
basecoin 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`:
```
basecoin 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`:
```
basecoin 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:
```
basecoin 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:
```
basecoin 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`:
```
basecoin 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!
```
basecoin 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!

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

@ -0,0 +1,23 @@
# Install
On a good day, basecoin can be installed like a normal Go program:
```
go get -u github.com/tendermint/basecoin/cmd/basecoin
```
In some cases, if that fails, or if another branch is required,
we use `glide` for dependency management.
The correct way of compiling from source, assuming you've already
run `go get` or otherwise cloned the repo, is:
```
cd $GOPATH/src/github.com/tendermint/basecoin
git checkout develop # (until we release v0.9)
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, hash []byte, header *abci.Header) {
}
func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock {
return abci.ResponseEndBlock{}
}

View File

@ -1,7 +0,0 @@
[
"base/chainID", "test_chain_id",
"base/account", {
"pub_key": [1, "67D3B5EAF0C0BF6B5A602D359DAECC86A7A74053490EC37AE08E71360587C870"],
"balance": 9007199254740992
}
]

37
glide.lock generated
View File

@ -1,5 +1,5 @@
hash: 3869944d14a8df914ffcad02c2ef3548173daba51c5ea697767f8af77c07b348
updated: 2017-01-15T14:45:40.368426139-08:00
updated: 2017-01-29T22:09:11.408245895-08:00
imports:
- name: github.com/btcsuite/btcd
version: afec1bd1245a4a19e6dfe1306974b733e7cbb9b8
@ -9,6 +9,8 @@ imports:
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/BurntSushi/toml
version: 99064174e013895bbd9b025c31100bd1d9b590ca
- name: github.com/ebuchman/fail-test
version: c1eddaa09da2b4017351245b0d43234955276798
- name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf
@ -25,6 +27,8 @@ imports:
version: ed8eb9e318d7a84ce5915b495b7d35e0cfe7b5a8
- name: github.com/mattn/go-isatty
version: 66b8e73f3f5cda9f96b69efd03dd3d7fc4a5cdb8
- name: github.com/pkg/errors
version: 248dadf4e9068a0b3e79f02ed0a610d935de5302
- name: github.com/syndtr/goleveldb
version: 6ae1797c0b42b9323fc27ff7dcf568df88f2f33d
subpackages:
@ -41,9 +45,11 @@ imports:
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: 05096de3687ac582bec63860b3dd384acd9149aa
version: 8df0bc3a40ccad0d2be10e33c62c404e65c92502
subpackages:
- client
- example/dummy
- example/nil
- server
- types
- name: github.com/tendermint/ed25519
@ -51,8 +57,12 @@ imports:
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-autofile
version: 0416e0aa9c68205aa44844096f9f151ada9d0405
- name: github.com/tendermint/go-clist
version: 3baa390bbaf7634251c42ad69a8682e7e3990552
- name: github.com/tendermint/go-common
version: 70e694ee76f09058ea38c9ba81b4aa621bd54df1
version: 339e135776142939d82bc8e699db0bf391fd938d
- name: github.com/tendermint/go-config
version: e64b424499acd0eb9856b88e10c0dff41628c0d6
- name: github.com/tendermint/go-crypto
@ -68,7 +78,7 @@ imports:
- name: github.com/tendermint/go-logger
version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2
- name: github.com/tendermint/go-merkle
version: 2979c7eb8aa020fa1cf203654907dbb889703888
version: 653cb1f631528351ddbc359b994eb0c96f0341cd
- name: github.com/tendermint/go-p2p
version: 67c9086b7458eb45b1970483decd01cd744c477a
subpackages:
@ -77,23 +87,36 @@ imports:
version: 6177eb8398ebd4613fbecb71fd96d7c7d97303ec
subpackages:
- client
- server
- types
- name: github.com/tendermint/go-wire
version: 2f3b7aafe21c80b19b6ee3210ecb3e3d07c7a471
version: 3216ec9d47bbdf8d4fc27d22169ea86a6688bc15
- name: github.com/tendermint/log15
version: 9545b249b3aacafa97f79e0838b02b274adc6f5f
subpackages:
- term
- name: github.com/tendermint/merkleeyes
version: 2cf87e5f049ab6131aa4ea188c1b5b629d9b3bf9
version: 7c1ec0ef86c42b7a461e3967efb6c35bd5652101
subpackages:
- app
- client
- name: github.com/tendermint/tendermint
version: cf0cb9558aaecbf3ddb071eb863df77e55d828ed
version: 67ab574e9889c0641ae959296d391e3cadec55e3
subpackages:
- blockchain
- config/tendermint
- consensus
- mempool
- node
- proxy
- rpc/core
- rpc/core/types
- rpc/grpc
- state
- types
- version
- name: github.com/urfave/cli
version: 8ef3805c9de2519805c3f060524b695bba2cd715
- name: golang.org/x/crypto
version: aa2481cbfe81d911eb62b642b7a6b5ec58bbea71
subpackages:

View File

@ -18,6 +18,5 @@ import:
version: develop
- package: github.com/tendermint/abci
version: develop
- package: github.com/gorilla/websocket
version: v1.1.0

View File

@ -10,32 +10,29 @@ import (
type CounterPluginState struct {
Counter int
TotalCost types.Coins
TotalFees types.Coins
}
type CounterTx struct {
Valid bool
Cost types.Coins
Fee types.Coins
}
//--------------------------------------------------------------------------------
type CounterPlugin struct {
name string
}
func (cp *CounterPlugin) Name() string {
return cp.name
return "counter"
}
func (cp *CounterPlugin) StateKey() []byte {
return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name))
return []byte(fmt.Sprintf("CounterPlugin.State"))
}
func NewCounterPlugin(name string) *CounterPlugin {
return &CounterPlugin{
name: name,
}
func New() *CounterPlugin {
return &CounterPlugin{}
}
func (cp *CounterPlugin) SetOption(store types.KVStore, key string, value string) (log string) {
@ -43,32 +40,31 @@ func (cp *CounterPlugin) SetOption(store types.KVStore, key string, value string
}
func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
var tx CounterTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()).PrependLog("CounterTx Error: ")
}
// Validate tx
if !tx.Valid {
return abci.ErrInternalError.AppendLog("CounterTx.Valid must be true")
}
if !tx.Cost.IsValid() {
return abci.ErrInternalError.AppendLog("CounterTx.Cost is not sorted or has zero amounts")
if !tx.Fee.IsValid() {
return abci.ErrInternalError.AppendLog("CounterTx.Fee is not sorted or has zero amounts")
}
if !tx.Cost.IsNonnegative() {
return abci.ErrInternalError.AppendLog("CounterTx.Cost must be nonnegative")
if !tx.Fee.IsNonnegative() {
return abci.ErrInternalError.AppendLog("CounterTx.Fee must be nonnegative")
}
// Did the caller provide enough coins?
if !ctx.Coins.IsGTE(tx.Cost) {
return abci.ErrInsufficientFunds.AppendLog("CounterTx.Cost was not provided")
if !ctx.Coins.IsGTE(tx.Fee) {
return abci.ErrInsufficientFunds.AppendLog("CounterTx.Fee was not provided")
}
// TODO If there are any funds left over, return funds.
// e.g. !ctx.Coins.Minus(tx.Cost).IsZero()
// e.g. !ctx.Coins.Minus(tx.Fee).IsZero()
// ctx.CallerAccount is synced w/ store, so just modify that and store it.
// Load CounterPluginState
@ -83,7 +79,7 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
// Update CounterPluginState
cpState.Counter += 1
cpState.TotalCost = cpState.TotalCost.Plus(tx.Cost)
cpState.TotalFees = cpState.TotalFees.Plus(tx.Fee)
// Save CounterPluginState
store.Set(cp.StateKey(), wire.BinaryBytes(cpState))
@ -94,9 +90,9 @@ func (cp *CounterPlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
func (cp *CounterPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
}
func (cp *CounterPlugin) BeginBlock(store types.KVStore, height uint64) {
func (cp *CounterPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
}
func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator {
return nil
func (cp *CounterPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) {
return
}

View File

@ -15,15 +15,14 @@ import (
func TestCounterPlugin(t *testing.T) {
// Basecoin initialization
eyesCli := eyescli.NewLocalClient()
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
t.Log(bcApp.Info())
// Add Counter plugin
counterPluginName := "testcounter"
counterPlugin := NewCounterPlugin(counterPluginName)
counterPlugin := New()
bcApp.RegisterPlugin(counterPlugin)
// Account initialization
@ -35,14 +34,14 @@ func TestCounterPlugin(t *testing.T) {
bcApp.SetOption("base/account", string(wire.JSONBytes(test1Acc)))
// Deliver a CounterTx
DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, cost types.Coins) abci.Result {
DeliverCounterTx := func(gas int64, fee types.Coin, inputCoins types.Coins, inputSequence int, appFee types.Coins) abci.Result {
// Construct an AppTx signature
tx := &types.AppTx{
Gas: gas,
Fee: fee,
Name: counterPluginName,
Name: "counter",
Input: types.NewTxInput(test1Acc.PubKey, inputCoins, inputSequence),
Data: wire.BinaryBytes(CounterTx{Valid: true, Cost: cost}),
Data: wire.BinaryBytes(CounterTx{Valid: true, Fee: appFee}),
}
// Sign request
@ -57,7 +56,7 @@ func TestCounterPlugin(t *testing.T) {
return bcApp.DeliverTx(txBytes)
}
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, cost) {
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) {
// Test a basic send, no fee
res := DeliverCounterTx(0, types.Coin{}, types.Coins{{"", 1}}, 1, types.Coins{})
@ -75,15 +74,15 @@ func TestCounterPlugin(t *testing.T) {
res = DeliverCounterTx(0, types.Coin{"", 2}, types.Coins{{"", 3}}, 3, types.Coins{})
assert.True(t, res.IsOK(), res.String())
// Test input equals fee+cost
// Test input equals fee+appFee
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 4, types.Coins{{"", 2}, {"gold", 1}})
assert.True(t, res.IsOK(), res.String())
// Test fee+cost prevented transaction, not enough ""
// Test fee+appFee prevented transaction, not enough ""
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 2}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 1}})
assert.True(t, res.IsErr(), res.String())
// Test fee+cost prevented transaction, not enough "gold"
// Test fee+appFee prevented transaction, not enough "gold"
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 1}}, 5, types.Coins{{"", 2}, {"gold", 2}})
assert.True(t, res.IsErr(), res.String())
@ -95,5 +94,5 @@ func TestCounterPlugin(t *testing.T) {
res = DeliverCounterTx(0, types.Coin{"", 1}, types.Coins{{"", 3}, {"gold", 2}}, 7, types.Coins{{"", 2}, {"gold", 1}})
assert.True(t, res.IsOK(), res.String())
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, cost) {
// REF: DeliverCounterTx(gas, fee, inputCoins, inputSequence, appFee) {
}

456
plugins/ibc/ibc.go Normal file
View File

@ -0,0 +1,456 @@
package ibc
import (
"errors"
"net/url"
"strings"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
merkle "github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire"
tm "github.com/tendermint/tendermint/types"
)
const (
// Key parts
_IBC = "ibc"
_BLOCKCHAIN = "blockchain"
_GENESIS = "genesis"
_STATE = "state"
_HEADER = "header"
_EGRESS = "egress"
_INGRESS = "ingress"
_CONNECTION = "connection"
)
type IBCPluginState struct {
// @[:ibc, :blockchain, :genesis, ChainID] <~ BlockchainGenesis
// @[:ibc, :blockchain, :state, ChainID] <~ BlockchainState
// @[:ibc, :blockchain, :header, ChainID, Height] <~ tm.Header
// @[:ibc, :egress, Src, Dst, Sequence] <~ Packet
// @[:ibc, :ingress, Dst, Src, Sequence] <~ Packet
// @[:ibc, :connection, Src, Dst] <~ Connection # TODO - keep connection state
}
type BlockchainGenesis struct {
ChainID string
Genesis string
}
type BlockchainState struct {
ChainID string
Validators []*tm.Validator
LastBlockHash []byte
LastBlockHeight uint64
}
type Packet struct {
SrcChainID string
DstChainID string
Sequence uint64
Type string
Payload []byte
}
//--------------------------------------------------------------------------------
const (
IBCTxTypeRegisterChain = byte(0x01)
IBCTxTypeUpdateChain = byte(0x02)
IBCTxTypePacketCreate = byte(0x03)
IBCTxTypePacketPost = byte(0x04)
IBCCodeEncodingError = abci.CodeType(1001)
IBCCodeChainAlreadyExists = abci.CodeType(1002)
IBCCodePacketAlreadyExists = abci.CodeType(1003)
IBCCodeUnknownHeight = abci.CodeType(1004)
IBCCodeInvalidCommit = abci.CodeType(1005)
IBCCodeInvalidProof = abci.CodeType(1006)
)
var _ = wire.RegisterInterface(
struct{ IBCTx }{},
wire.ConcreteType{IBCRegisterChainTx{}, IBCTxTypeRegisterChain},
wire.ConcreteType{IBCUpdateChainTx{}, IBCTxTypeUpdateChain},
wire.ConcreteType{IBCPacketCreateTx{}, IBCTxTypePacketCreate},
wire.ConcreteType{IBCPacketPostTx{}, IBCTxTypePacketPost},
)
type IBCTx interface {
AssertIsIBCTx()
ValidateBasic() abci.Result
}
func (IBCRegisterChainTx) AssertIsIBCTx() {}
func (IBCUpdateChainTx) AssertIsIBCTx() {}
func (IBCPacketCreateTx) AssertIsIBCTx() {}
func (IBCPacketPostTx) AssertIsIBCTx() {}
type IBCRegisterChainTx struct {
BlockchainGenesis
}
func (IBCRegisterChainTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
type IBCUpdateChainTx struct {
Header tm.Header
Commit tm.Commit
// TODO: NextValidators
}
func (IBCUpdateChainTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
type IBCPacketCreateTx struct {
Packet
}
func (IBCPacketCreateTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
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
}
func (IBCPacketPostTx) ValidateBasic() (res abci.Result) {
// TODO - validate
return
}
//--------------------------------------------------------------------------------
type IBCPlugin struct {
}
func (ibc *IBCPlugin) Name() string {
return "IBC"
}
func (ibc *IBCPlugin) StateKey() []byte {
return []byte("IBCPlugin.State")
}
func New() *IBCPlugin {
return &IBCPlugin{}
}
func (ibc *IBCPlugin) SetOption(store types.KVStore, key string, value string) (log string) {
return ""
}
func (ibc *IBCPlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
var tx IBCTx
err := wire.ReadBinaryBytes(txBytes, &tx)
if err != nil {
return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error())
}
// Validate tx
res = tx.ValidateBasic()
if res.IsErr() {
return res.PrependLog("ValidateBasic Failed: ")
}
// TODO - Check whether sufficient funds
defer func() {
// TODO - Refund any remaining funds left over
// e.g. !ctx.Coins.Minus(tx.Fee).IsZero()
// ctx.CallerAccount is synced w/ store, so just modify that and store it.
// NOTE: We should use the CallContext to store fund/refund information.
}()
sm := &IBCStateMachine{store, ctx, abci.OK}
switch tx := tx.(type) {
case IBCRegisterChainTx:
sm.runRegisterChainTx(tx)
case IBCUpdateChainTx:
sm.runUpdateChainTx(tx)
case IBCPacketCreateTx:
sm.runPacketCreateTx(tx)
case IBCPacketPostTx:
sm.runPacketPostTx(tx)
}
return sm.res
}
type IBCStateMachine struct {
store types.KVStore
ctx types.CallContext
res abci.Result
}
func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) {
chainGenKey := toKey(_IBC, _BLOCKCHAIN, _GENESIS, tx.ChainID)
chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, tx.ChainID)
chainGen := tx.BlockchainGenesis
// Parse genesis
var chainGenDoc = &tm.GenesisDoc{}
var err error
wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err)
if err != nil {
sm.res.Code = IBCCodeEncodingError
sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error()
return
}
// Make sure chainGen doesn't already exist
if exists(sm.store, chainGenKey) {
sm.res.Code = IBCCodeChainAlreadyExists
sm.res.Log = "Already exists"
return
}
// Save new BlockchainGenesis
save(sm.store, chainGenKey, chainGen)
// Create new BlockchainState
chainState := BlockchainState{
ChainID: chainGenDoc.ChainID,
Validators: make([]*tm.Validator, len(chainGenDoc.Validators)),
LastBlockHash: nil,
LastBlockHeight: 0,
}
// Make validators slice
for i, val := range chainGenDoc.Validators {
pubKey := val.PubKey
address := pubKey.Address()
chainState.Validators[i] = &tm.Validator{
Address: address,
PubKey: pubKey,
VotingPower: val.Amount,
}
}
// Save new BlockchainState
save(sm.store, chainStateKey, chainState)
}
func (sm *IBCStateMachine) runUpdateChainTx(tx IBCUpdateChainTx) {
chainID := tx.Header.ChainID
chainStateKey := toKey(_IBC, _BLOCKCHAIN, _STATE, chainID)
// Make sure chainState exists
if !exists(sm.store, chainStateKey) {
return // Chain does not exist, do nothing
}
// Load latest chainState
var chainState BlockchainState
exists, err := load(sm.store, chainStateKey, &chainState)
if err != nil {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading ChainState: %v", err.Error()))
return
}
if !exists {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Missing ChainState"))
return
}
// Check commit against last known state & validators
err = verifyCommit(chainState, &tx.Header, &tx.Commit)
if err != nil {
sm.res.Code = IBCCodeInvalidCommit
sm.res.Log = cmn.Fmt("Invalid Commit: %v", err.Error())
return
}
// Store header
headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER, chainID, cmn.Fmt("%v", tx.Header.Height))
save(sm.store, headerKey, tx.Header)
// Update chainState
chainState.LastBlockHash = tx.Header.Hash()
chainState.LastBlockHeight = uint64(tx.Header.Height)
// Store chainState
save(sm.store, chainStateKey, chainState)
}
func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
packet := tx.Packet
packetKey := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
// Make sure packet doesn't already exist
if exists(sm.store, packetKey) {
sm.res.Code = IBCCodePacketAlreadyExists
// TODO: .AppendLog() does not update sm.res
sm.res.Log = "Already exists"
return
}
// Save new Packet
save(sm.store, packetKey, packet)
}
func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
packet := tx.Packet
packetKeyEgress := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
packetKeyIngress := toKey(_IBC, _INGRESS,
packet.DstChainID,
packet.SrcChainID,
cmn.Fmt("%v", packet.Sequence),
)
headerKey := toKey(_IBC, _BLOCKCHAIN, _HEADER,
tx.FromChainID,
cmn.Fmt("%v", tx.FromChainHeight),
)
// Make sure packet doesn't already exist
if exists(sm.store, packetKeyIngress) {
sm.res.Code = IBCCodePacketAlreadyExists
sm.res.Log = "Already exists"
return
}
// Save new Packet
save(sm.store, packetKeyIngress, packet)
// Load Header and make sure it exists
var header tm.Header
exists, err := load(sm.store, headerKey, &header)
if err != nil {
sm.res = abci.ErrInternalError.AppendLog(cmn.Fmt("Loading Header: %v", err.Error()))
return
}
if !exists {
sm.res.Code = IBCCodeUnknownHeight
sm.res.Log = cmn.Fmt("Loading Header: Unknown height")
return
}
/*
// Read Proof
var proof *merkle.IAVLProof
err = wire.ReadBinaryBytes(tx.Proof, &proof)
if err != nil {
sm.res.Code = IBCEncodingError
sm.res.Log = cmn.Fmt("Reading Proof: %v", err.Error())
return
}
*/
proof := tx.Proof
if proof == nil {
sm.res.Code = IBCCodeInvalidProof
sm.res.Log = "Proof is nil"
return
}
packetBytes := wire.BinaryBytes(packet)
// Make sure packet's proof matches given (packet, key, blockhash)
ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
if !ok {
sm.res.Code = IBCCodeInvalidProof
sm.res.Log = "Proof is invalid"
return
}
return
}
func (ibc *IBCPlugin) InitChain(store types.KVStore, vals []*abci.Validator) {
}
func (cp *IBCPlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) {
}
func (cp *IBCPlugin) EndBlock(store types.KVStore, height uint64) (res abci.ResponseEndBlock) {
return
}
//--------------------------------------------------------------------------------
// TODO: move to utils
// Returns true if exists, false if nil.
func exists(store types.KVStore, key []byte) (exists bool) {
value := store.Get(key)
return len(value) > 0
}
// Load bytes from store by reading value for key and read into ptr.
// Returns true if exists, false if nil.
// Returns err if decoding error.
func load(store types.KVStore, key []byte, ptr interface{}) (exists bool, err error) {
value := store.Get(key)
if len(value) > 0 {
err = wire.ReadBinaryBytes(value, ptr)
if err != nil {
return true, errors.New(
cmn.Fmt("Error decoding key 0x%X = 0x%X: %v", key, value, err.Error()),
)
}
return true, nil
} else {
return false, nil
}
}
// Save bytes to store by writing obj's go-wire binary bytes.
func save(store types.KVStore, key []byte, obj interface{}) {
store.Set(key, wire.BinaryBytes(obj))
}
// Key parts are URL escaped and joined with ','
func toKey(parts ...string) []byte {
escParts := make([]string, len(parts))
for i, part := range parts {
escParts[i] = url.QueryEscape(part)
}
return []byte(strings.Join(escParts, ","))
}
// NOTE: Commit's votes include ValidatorAddress, so can be matched up
// against chainState.Validators, even if the validator set had changed.
// For the purpose of the demo, we assume that the validator set hadn't changed,
// though we should check that explicitly.
func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Commit) error {
// Ensure that chainState and header ChainID match.
if chainState.ChainID != header.ChainID {
return errors.New(cmn.Fmt("Expected header.ChainID %v, got %v", chainState.ChainID, header.ChainID))
}
if len(chainState.Validators) == 0 {
return errors.New(cmn.Fmt("Blockchain has no validators")) // NOTE: Why would this happen?
}
if len(commit.Precommits) == 0 {
return errors.New(cmn.Fmt("Commit has no signatures"))
}
chainID := chainState.ChainID
vote0 := commit.Precommits[0]
vals := chainState.Validators
valSet := tm.NewValidatorSet(vals)
// NOTE: Currently this only works with the exact same validator set.
// Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose
// the functionality to verify commits even after validator changes.
err := valSet.VerifyCommit(chainID, vote0.BlockID, vote0.Height, commit)
if err != nil {
return err
}
// All ok!
return nil
}

397
plugins/ibc/ibc_test.go Normal file
View File

@ -0,0 +1,397 @@
package ibc
import (
"bytes"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/testutils"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
tm "github.com/tendermint/tendermint/types"
)
// NOTE: PrivAccounts are sorted by Address,
// GenesisDoc, not necessarily.
func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAccount) {
var privAccs []types.PrivAccount
genDoc := &tm.GenesisDoc{
ChainID: chainID,
Validators: nil,
}
for i := 0; i < numVals; i++ {
name := cmn.Fmt("%v_val_%v", chainID, i)
privAcc := testutils.PrivAccountFromSecret(name)
genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{
PubKey: privAcc.Account.PubKey,
Amount: 1,
Name: name,
})
privAccs = append(privAccs, privAcc)
}
// Sort PrivAccounts
sort.Sort(PrivAccountsByAddress(privAccs))
return genDoc, privAccs
}
//-------------------------------------
// Implements sort for sorting PrivAccount by address.
type PrivAccountsByAddress []types.PrivAccount
func (pas PrivAccountsByAddress) Len() int {
return len(pas)
}
func (pas PrivAccountsByAddress) Less(i, j int) bool {
return bytes.Compare(pas[i].Account.PubKey.Address(), pas[j].Account.PubKey.Address()) == -1
}
func (pas PrivAccountsByAddress) Swap(i, j int) {
it := pas[i]
pas[i] = pas[j]
pas[j] = it
}
//--------------------------------------------------------------------------------
func TestIBCPlugin(t *testing.T) {
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.CallContext{
CallerAddress: nil,
CallerAccount: nil,
Coins: types.Coins{},
}
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1)
// Register a malformed chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: "<THIS IS NOT JSON>",
},
}}))
assert.Equal(t, IBCCodeEncodingError, res.Code)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Successfully register a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.True(t, res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Duplicate request fails
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.Equal(t, IBCCodeChainAlreadyExists, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Create a new packet (for testing)
packet := Packet{
SrcChainID: "test_chain",
DstChainID: "dst_chain",
Sequence: 0,
Type: "data",
Payload: []byte("hello world"),
}
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(t, abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Post a duplicate packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(t, IBCCodePacketAlreadyExists, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Construct a Header that includes the above packet.
store.Sync()
resCommit := eyesClient.CommitSync()
appHash := resCommit.Data
header := tm.Header{
ChainID: "test_chain",
Height: 999,
AppHash: appHash,
ValidatorsHash: []byte("must_exist"), // TODO make optional
}
// Construct a Commit that signs above header
blockHash := header.Hash()
blockID := tm.BlockID{Hash: blockHash}
commit := tm.Commit{
BlockID: blockID,
Precommits: make([]*tm.Vote, len(privAccs_1)),
}
for i, privAcc := range privAccs_1 {
vote := &tm.Vote{
ValidatorAddress: privAcc.Account.PubKey.Address(),
ValidatorIndex: i,
Height: 999,
Round: 0,
Type: tm.VoteTypePrecommit,
BlockID: tm.BlockID{Hash: blockHash},
}
vote.Signature = privAcc.PrivKey.Sign(
tm.SignBytes("test_chain", vote),
)
commit.Precommits[i] = vote
}
// Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assert.Equal(t, abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Get proof for the packet
packetKey := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
resQuery, err := eyesClient.QuerySync(abci.RequestQuery{
Path: "/store",
Data: packetKey,
Prove: true,
})
assert.Nil(t, err)
var proof *merkle.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(t, err)
// Post a packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
FromChainID: "test_chain",
FromChainHeight: 999,
Packet: packet,
Proof: proof,
}}))
assert.Equal(t, abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
}
func TestIBCPluginBadCommit(t *testing.T) {
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.CallContext{
CallerAddress: nil,
CallerAccount: nil,
Coins: types.Coins{},
}
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1)
// Successfully register a chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.True(t, res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Construct a Header
header := tm.Header{
ChainID: "test_chain",
Height: 999,
ValidatorsHash: []byte("must_exist"), // TODO make optional
}
// Construct a Commit that signs above header
blockHash := header.Hash()
blockID := tm.BlockID{Hash: blockHash}
commit := tm.Commit{
BlockID: blockID,
Precommits: make([]*tm.Vote, len(privAccs_1)),
}
for i, privAcc := range privAccs_1 {
vote := &tm.Vote{
ValidatorAddress: privAcc.Account.PubKey.Address(),
ValidatorIndex: i,
Height: 999,
Round: 0,
Type: tm.VoteTypePrecommit,
BlockID: tm.BlockID{Hash: blockHash},
}
vote.Signature = privAcc.PrivKey.Sign(
tm.SignBytes("test_chain", vote),
)
commit.Precommits[i] = vote
}
// Update a chain with a broken commit
// Modify the first byte of the first signature
sig := commit.Precommits[0].Signature.(crypto.SignatureEd25519)
sig[0] += 1
commit.Precommits[0].Signature = sig
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assert.Equal(t, IBCCodeInvalidCommit, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
}
func TestIBCPluginBadProof(t *testing.T) {
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.CallContext{
CallerAddress: nil,
CallerAccount: nil,
Coins: types.Coins{},
}
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1)
// Successfully register a chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.True(t, res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Create a new packet (for testing)
packet := Packet{
SrcChainID: "test_chain",
DstChainID: "dst_chain",
Sequence: 0,
Type: "data",
Payload: []byte("hello world"),
}
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(t, abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Construct a Header that includes the above packet.
store.Sync()
resCommit := eyesClient.CommitSync()
appHash := resCommit.Data
header := tm.Header{
ChainID: "test_chain",
Height: 999,
AppHash: appHash,
ValidatorsHash: []byte("must_exist"), // TODO make optional
}
// Construct a Commit that signs above header
blockHash := header.Hash()
blockID := tm.BlockID{Hash: blockHash}
commit := tm.Commit{
BlockID: blockID,
Precommits: make([]*tm.Vote, len(privAccs_1)),
}
for i, privAcc := range privAccs_1 {
vote := &tm.Vote{
ValidatorAddress: privAcc.Account.PubKey.Address(),
ValidatorIndex: i,
Height: 999,
Round: 0,
Type: tm.VoteTypePrecommit,
BlockID: tm.BlockID{Hash: blockHash},
}
vote.Signature = privAcc.PrivKey.Sign(
tm.SignBytes("test_chain", vote),
)
commit.Precommits[i] = vote
}
// Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assert.Equal(t, abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Get proof for the packet
packetKey := toKey(_IBC, _EGRESS,
packet.SrcChainID,
packet.DstChainID,
cmn.Fmt("%v", packet.Sequence),
)
resQuery, err := eyesClient.QuerySync(abci.RequestQuery{
Path: "/store",
Data: packetKey,
Prove: true,
})
assert.Nil(t, err)
var proof *merkle.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(t, err)
// Mutate the proof
proof.InnerNodes[0].Height += 1
// Post a packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
FromChainID: "test_chain",
FromChainHeight: 999,
Packet: packet,
Proof: proof,
}}))
assert.Equal(t, IBCCodeInvalidProof, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
}

View File

@ -12,7 +12,7 @@ import (
)
func TestSendTx(t *testing.T) {
eyesCli := eyescli.NewLocalClient()
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)
@ -58,7 +58,7 @@ func TestSendTx(t *testing.T) {
}
func TestSequence(t *testing.T) {
eyesCli := eyescli.NewLocalClient()
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetOption("base/chainID", chainID)

View File

@ -35,6 +35,8 @@ func (coins Coins) IsValid() bool {
if coin.Amount == 0 {
return false
}
// we compare each coin against the last denom
lowDenom = coin.Denom
}
return true
}

View File

@ -42,6 +42,19 @@ func TestCoinsBadSort(t *testing.T) {
}
}
func TestCoinsBadSort2(t *testing.T) {
// both are after the first one, but the second and third are in the wrong order
coins := Coins{
Coin{"GAS", 1},
Coin{"TREE", 1},
Coin{"MINERAL", 1},
}
if coins.IsValid() {
t.Fatal("Coins are not sorted")
}
}
func TestCoinsBadAmount(t *testing.T) {
coins := Coins{
Coin{"GAS", 1},

View File

@ -36,9 +36,11 @@ func (mkv *MemKVStore) Get(key []byte) (value []byte) {
// A Cache that enforces deterministic sync order.
type KVCache struct {
store KVStore
cache map[string]kvCacheValue
keys *list.List
store KVStore
cache map[string]kvCacheValue
keys *list.List
logging bool
logLines []string
}
type kvCacheValue struct {
@ -46,12 +48,28 @@ type kvCacheValue struct {
e *list.Element // The KVCache.keys element
}
// NOTE: If store is nil, creates a new MemKVStore
func NewKVCache(store KVStore) *KVCache {
if store == nil {
store = NewMemKVStore()
}
return (&KVCache{
store: store,
}).Reset()
}
func (kvc *KVCache) SetLogging() {
kvc.logging = true
}
func (kvc *KVCache) GetLogLines() []string {
return kvc.logLines
}
func (kvc *KVCache) ClearLogLines() {
kvc.logLines = nil
}
func (kvc *KVCache) Reset() *KVCache {
kvc.cache = make(map[string]kvCacheValue)
kvc.keys = list.New()
@ -59,7 +77,10 @@ func (kvc *KVCache) Reset() *KVCache {
}
func (kvc *KVCache) Set(key []byte, value []byte) {
fmt.Println("Set [KVCache]", formatBytes(key), "=", formatBytes(value))
if kvc.logging {
line := fmt.Sprintf("Set %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
cacheValue, ok := kvc.cache[string(key)]
if ok {
kvc.keys.MoveToBack(cacheValue.e)
@ -73,7 +94,10 @@ func (kvc *KVCache) Set(key []byte, value []byte) {
func (kvc *KVCache) Get(key []byte) (value []byte) {
cacheValue, ok := kvc.cache[string(key)]
if ok {
fmt.Println("GET [KVCache, hit]", formatBytes(key), "=", formatBytes(cacheValue.v))
if kvc.logging {
line := fmt.Sprintf("Get (hit) %v = %v", LegibleBytes(key), LegibleBytes(cacheValue.v))
kvc.logLines = append(kvc.logLines, line)
}
return cacheValue.v
} else {
value := kvc.store.Get(key)
@ -81,7 +105,10 @@ func (kvc *KVCache) Get(key []byte) (value []byte) {
v: value,
e: kvc.keys.PushBack(key),
}
fmt.Println("GET [KVCache, miss]", formatBytes(key), "=", formatBytes(value))
if kvc.logging {
line := fmt.Sprintf("Get (miss) %v = %v", LegibleBytes(key), LegibleBytes(value))
kvc.logLines = append(kvc.logLines, line)
}
return value
}
}
@ -97,13 +124,13 @@ func (kvc *KVCache) Sync() {
//----------------------------------------
func formatBytes(data []byte) string {
func LegibleBytes(data []byte) string {
s := ""
for _, b := range data {
if 0x21 <= b && b < 0x7F {
s += Green(string(b))
} else {
s += Blue(Fmt("%X", b))
s += Blue(Fmt("%02X", b))
}
}
return s

View File

@ -2,26 +2,31 @@ package types
import (
"fmt"
abci "github.com/tendermint/abci/types"
)
type Plugin interface {
// Name of this plugin, should be short.
Name() string
SetOption(store KVStore, key string, value string) (log 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
BeginBlock(store KVStore, hash []byte, header *abci.Header)
EndBlock(store KVStore, height uint64) abci.ResponseEndBlock
}
//----------------------------------------
// CallContext.Caller's coins have been deducted by CallContext.Coins
// Caller's Sequence has been incremented.
type CallContext struct {
CallerAddress []byte
CallerAccount *Account
Coins Coins
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
}
func NewCallContext(callerAddress []byte, callerAccount *Account, coins Coins) CallContext {