Merge branch 'develop'

This commit is contained in:
Ethan Frey 2017-05-29 16:19:38 +02:00
commit 7b39417b99
62 changed files with 2397 additions and 807 deletions

View File

@ -1,5 +1,30 @@
# Changelog
## 0.5.0 (May 27, 2017)
BREAKING CHANGES:
- only those related to the tendermint 0.9 -> 0.10 upgrade
ENHANCEMENTS:
- basecoin cli
- integrates tendermint 0.10.0 and unifies cli (init, unsafe_reset_all, ...)
- integrate viper, all command line flags can also be defined in environmental variables or config.toml
- genesis file
- you can define accounts with either address or pub_key
- sorts coins for you, so no silent errors if not in alphabetical order
- [light-client](https://github.com/tendermint/light-client) integration
- no longer must you trust the node you connect to, prove everything!
- new [basecli command](./cmd/basecli/README.md)
- integrated [key management](https://github.com/tendermint/go-crypto/blob/master/cmd/README.md), stored encrypted locally
- tracks validator set changes and proves everything from one initial validator seed
- `basecli proof state` gets complete proofs for any abci state
- `basecli proof tx` gets complete proof where a tx was stored in the chain
- `basecli proxy` exposes tendermint rpc, but only passes through results after doing complete verification
BUG FIXES:
- no more silently ignored error with invalid coin names (eg. "17.22foo coin" used to parse as "17 foo", not warning/error)
## 0.4.1 (April 26, 2017)
BUG FIXES:
@ -74,7 +99,7 @@ We also changed `chainID` to `chain_id` and consolidated to have just one of the
FEATURES:
- Introduce `basecoin init` and `basecoin unsafe_reset_all`
- Introduce `basecoin init` and `basecoin unsafe_reset_all`
## 0.2.0 (March 6, 2017)
@ -86,8 +111,8 @@ BREAKING CHANGES:
FEATURES:
- CLI for sending transactions and querying the state,
designed to be easily extensible as plugins are implemented
- CLI for sending transactions and querying the state,
designed to be easily extensible as plugins are implemented
- Run Basecoin in-process with Tendermint
- Add `/account` path in Query
- IBC plugin for InterBlockchain Communication

View File

@ -1,13 +1,15 @@
package app
import (
"encoding/hex"
"encoding/json"
"strings"
abci "github.com/tendermint/abci/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
. "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
sm "github.com/tendermint/basecoin/state"
"github.com/tendermint/basecoin/types"
@ -24,6 +26,7 @@ type Basecoin struct {
state *sm.State
cacheState *sm.State
plugins *types.Plugins
logger log.Logger
}
func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
@ -34,9 +37,15 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin {
state: state,
cacheState: nil,
plugins: plugins,
logger: log.NewNopLogger(),
}
}
func (app *Basecoin) SetLogger(l log.Logger) {
app.logger = l
app.state.SetLogger(l.With("module", "state"))
}
// XXX For testing, not thread safe!
func (app *Basecoin) GetState() *sm.State {
return app.state.CacheWrap()
@ -60,7 +69,7 @@ func (app *Basecoin) SetOption(key string, value string) string {
if plugin == nil {
return "Invalid plugin name: " + pluginName
}
log.Notice("SetOption on plugin", "plugin", pluginName, "key", key, "value", value)
app.logger.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value)
return plugin.SetOption(app.state, key, value)
} else {
// Set option on basecoin
@ -69,13 +78,18 @@ func (app *Basecoin) SetOption(key string, value string) string {
app.state.SetChainID(value)
return "Success"
case "account":
var acc types.Account
var acc GenesisAccount
err := json.Unmarshal([]byte(value), &acc)
if err != nil {
return "Error decoding acc message: " + err.Error()
}
app.state.SetAccount(acc.PubKey.Address(), &acc)
log.Notice("SetAccount", "addr", acc.PubKey.Address(), "acc", acc)
acc.Balance.Sort()
addr, err := acc.GetAddr()
if err != nil {
return "Invalid address: " + err.Error()
}
app.state.SetAccount(addr, acc.ToAccount())
app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc)
return "Success"
}
@ -136,7 +150,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu
// handle special path for account info
if reqQuery.Path == "/account" {
reqQuery.Path = "/key"
reqQuery.Data = sm.AccountKey(reqQuery.Data)
reqQuery.Data = types.AccountKey(reqQuery.Data)
}
resQuery, err := app.eyesCli.QuerySync(reqQuery)

View File

@ -1,8 +1,8 @@
package app
import (
"encoding/hex"
"encoding/json"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
@ -10,8 +10,9 @@ import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/go-wire"
wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
//--------------------------------------------------------
@ -36,7 +37,7 @@ func newAppTest(t *testing.T) *appTest {
// make a tx sending 5mycoin from each accIn to accOut
func (at *appTest) getTx(seq int) *types.SendTx {
tx := types.GetTx(seq, at.accOut, at.accIn)
tx := types.MakeSendTx(seq, at.accOut, at.accIn)
types.SignTx(at.chainID, tx, at.accIn)
return tx
}
@ -56,6 +57,7 @@ func (at *appTest) reset() {
eyesCli := eyes.NewLocalClient("", 0)
at.app = NewBasecoin(eyesCli)
at.app.SetLogger(log.TestingLogger().With("module", "app"))
res := at.app.SetOption("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success")
@ -101,9 +103,11 @@ func TestSplitKey(t *testing.T) {
func TestSetOption(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
app.SetLogger(log.TestingLogger().With("module", "app"))
//testing ChainID
chainID := "testChain"
@ -111,11 +115,43 @@ func TestSetOption(t *testing.T) {
assert.EqualValues(app.GetState().GetChainID(), chainID)
assert.EqualValues(res, "Success")
// make a nice account...
accIn := types.MakeAcc("input0")
accsInBytes, err := json.Marshal(accIn.Account)
assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes))
assert.EqualValues(res, "Success")
require.EqualValues(res, "Success")
// make sure it is set correctly, with some balance
acct := types.GetAccount(app.GetState(), accIn.PubKey.Address())
require.NotNil(acct)
assert.Equal(accIn.Balance, acct.Balance)
// let's parse an account with badly sorted coins...
unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1")
require.Nil(err)
unsortCoins := types.Coins{{"BTC", 789}, {"eth", 123}}
unsortAcc := `{
"pub_key": {
"type": "ed25519",
"data": "AD084F0572C116D618B36F2EB08240D1BAB4B51716CCE0E7734B89C8936DCE9A"
},
"coins": [
{
"denom": "eth",
"amount": 123
},
{
"denom": "BTC",
"amount": 789
}
]
}`
res = app.SetOption("base/account", unsortAcc)
require.EqualValues(res, "Success")
acct = types.GetAccount(app.GetState(), unsortAddr)
require.NotNil(acct)
assert.True(acct.Balance.IsValid())
assert.Equal(unsortCoins, acct.Balance)
res = app.SetOption("base/dslfkgjdas", "")
assert.NotEqual(res, "Success")
@ -125,6 +161,7 @@ func TestSetOption(t *testing.T) {
res = app.SetOption("dslfkgjdas/szfdjzs", "")
assert.NotEqual(res, "Success")
}
// Test CheckTx and DeliverTx with insufficient and sufficient balance
@ -176,7 +213,5 @@ func TestQuery(t *testing.T) {
Path: "/account",
Data: at.accIn.Account.PubKey.Address(),
})
fmt.Println(resQueryPreCommit)
fmt.Println(resQueryPostCommit)
assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
}

View File

@ -1,12 +1,15 @@
package app
import (
"bytes"
"encoding/json"
"github.com/pkg/errors"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
//tmtypes "github.com/tendermint/tendermint/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire/data"
cmn "github.com/tendermint/tmlibs/common"
)
func (app *Basecoin) LoadGenesis(path string) error {
@ -26,13 +29,13 @@ func (app *Basecoin) LoadGenesis(path string) error {
}
r := app.SetOption("base/account", string(accBytes))
// TODO: SetOption returns an error
log.Notice("Done setting Account via SetOption", "result", r)
app.logger.Info("Done setting Account via SetOption", "result", r)
}
// set plugin options
for _, kv := range genDoc.AppOptions.pluginOptions {
r := app.SetOption(kv.Key, kv.Value)
log.Notice("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value)
app.logger.Info("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value)
}
return nil
}
@ -49,7 +52,7 @@ type FullGenesisDoc struct {
}
type GenesisDoc struct {
Accounts []types.Account `json:"accounts"`
Accounts []GenesisAccount `json:"accounts"`
PluginOptions []json.RawMessage `json:"plugin_options"`
pluginOptions []keyValue // unmarshaled rawmessages
@ -61,11 +64,7 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) {
return nil, errors.Wrap(err, "loading genesis file")
}
// the tendermint genesis is go-wire
// tmGenesis := new(tmtypes.GenesisDoc)
// err = wire.ReadJSONBytes(bytes, tmGenesis)
// the basecoin genesis go-data :)
// the basecoin genesis go-wire/data :)
genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc)
if err != nil {
@ -102,3 +101,40 @@ func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) {
}
return kvz, nil
}
/**** code to parse accounts from genesis docs ***/
type GenesisAccount struct {
Address data.Bytes `json:"address"`
// this from types.Account (don't know how to embed this properly)
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance types.Coins `json:"coins"`
}
func (g GenesisAccount) ToAccount() *types.Account {
return &types.Account{
PubKey: g.PubKey,
Sequence: g.Sequence,
Balance: g.Balance,
}
}
func (g GenesisAccount) GetAddr() ([]byte, error) {
noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty()
if noAddr {
if noPk {
return nil, errors.New("No address given")
}
return g.PubKey.Address(), nil
}
if noPk { // but is addr...
return g.Address, nil
}
// now, we have both, make sure they check out
if bytes.Equal(g.Address, g.PubKey.Address()) {
return g.Address, nil
}
return nil, errors.New("Address and pubkey don't match")
}

View File

@ -7,12 +7,14 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/go-crypto"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
)
const genesisFilepath = "./testdata/genesis.json"
const genesisAcctFilepath = "./testdata/genesis2.json"
func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t)
@ -34,11 +36,15 @@ func TestLoadGenesis(t *testing.T) {
// make sure balance is proper
assert.Equal(2, len(acct.Balance))
assert.EqualValues(12345, acct.Balance[0].Amount)
assert.EqualValues("blank", acct.Balance[0].Denom)
assert.True(acct.Balance.IsValid())
// note, that we now sort them to be valid
assert.EqualValues(654321, acct.Balance[0].Amount)
assert.EqualValues("ETH", acct.Balance[0].Denom)
assert.EqualValues(12345, acct.Balance[1].Amount)
assert.EqualValues("blank", acct.Balance[1].Denom)
// and public key is parsed properly
apk := acct.PubKey.PubKey
apk := acct.PubKey.Unwrap()
require.NotNil(apk)
epk, ok := apk.(crypto.PubKeyEd25519)
if assert.True(ok) {
@ -46,13 +52,58 @@ func TestLoadGenesis(t *testing.T) {
}
}
// Fix for issue #89, change the parse format for accounts in genesis.json
func TestLoadGenesisAccountAddress(t *testing.T) {
assert, require := assert.New(t), require.New(t)
eyesCli := eyescli.NewLocalClient("", 0)
app := NewBasecoin(eyesCli)
err := app.LoadGenesis(genesisAcctFilepath)
require.Nil(err, "%+v", err)
// check the chain id
assert.Equal("addr_accounts_chain", app.GetState().GetChainID())
// make sure the accounts were set properly
cases := []struct {
addr string
exists bool
hasPubkey bool
coins types.Coins
}{
// this comes from a public key, should be stored proper (alice)
{"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, types.Coins{{"one", 111}}},
// this comes from an address, should be stored proper (bob)
{"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, types.Coins{{"two", 222}}},
// this one had a mismatched address and pubkey, should not store under either (carl)
{"1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is given addr
{"700BEC5ED18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is addr of the given pubkey
// this comes from a secp256k1 public key, should be stored proper (sam)
{"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, types.Coins{{"four", 444}}},
}
for _, tc := range cases {
addr, err := hex.DecodeString(tc.addr)
require.Nil(err, tc.addr)
acct := app.GetState().GetAccount(addr)
if !tc.exists {
assert.Nil(acct, tc.addr)
} else if assert.NotNil(acct, tc.addr) {
// it should and does exist...
assert.True(acct.Balance.IsValid())
assert.Equal(tc.coins, acct.Balance)
assert.Equal(!tc.hasPubkey, acct.PubKey.Empty(), tc.addr)
}
}
}
func TestParseGenesisList(t *testing.T) {
assert, require := assert.New(t), require.New(t)
bytes, err := cmn.ReadFile(genesisFilepath)
require.Nil(err, "loading genesis file %+v", err)
// the basecoin genesis go-data :)
// the basecoin genesis go-wire/data :)
genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc)
require.Nil(err, "unmarshaling genesis file %+v", err)

View File

@ -1,7 +0,0 @@
package app
import (
"github.com/tendermint/go-logger"
)
var log = logger.New("module", "app")

52
app/testdata/genesis2.json vendored Normal file
View File

@ -0,0 +1,52 @@
{
"chain_id": "addr_accounts_chain",
"app_options": {
"accounts": [{
"name": "alice",
"pub_key": {
"type": "ed25519",
"data": "DBD9A46C45868F0A37C92B53113C09B048FBD87B5FBC2F8B199052973B8FAA36"
},
"coins": [
{
"denom": "one",
"amount": 111
}
]
}, {
"name": "bob",
"address": "C471FB670E44D219EE6DF2FC284BE38793ACBCE1",
"coins": [
{
"denom": "two",
"amount": 222
}
]
}, {
"name": "carl",
"address": "1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9",
"pub_key": {
"type": "ed25519",
"data": "177C0AC45E86257F0708DC085D592AB22AAEECD1D26381B757F7C96135921858"
},
"coins": [
{
"denom": "three",
"amount": 333
}
]
}, {
"name": "sam",
"pub_key": {
"type": "secp256k1",
"data": "02AA8342F63CCCCE6DDB128525BA048CE0B2993DA3B4308746E1F216361A87651E"
},
"coins": [
{
"denom": "four",
"amount": 444
}
]
}]
}
}

View File

@ -1,26 +1,26 @@
machine:
environment:
GOPATH: /home/ubuntu/.go_workspace
REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
GOPATH: "$HOME/.go_workspace"
PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME"
REPO: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME"
PATH: "$GOPATH/bin:$PATH"
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 && go install ./cmd/basecoin"
- mkdir -p "$PROJECT_PARENT_PATH"
- ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$REPO"
- env
test:
override:
- "cd $REPO && glide install && go install ./cmd/basecoin"
- ls $GOPATH/bin
- "cd $REPO && make test"
- "cd $REPO/demo && bash start.sh"

58
cmd/basecli/LIGHT_NODE.md Normal file
View File

@ -0,0 +1,58 @@
# Run your own (super) lightweight node
In addition to providing command-line tooling that goes cryptographic verification
on all the data your receive from the node, we have implemented a proxy mode, that
allows you to run a super lightweight node. It does not follow the chain on
every block or even every header, but only as needed. But still providing the
same security as running a full non-validator node on your local machine.
Basically, it runs as a proxy that exposes the same rpc interface as the full node
and connects to a (potentially untrusted) full node. Every response is cryptographically
verified before being passed through, returning an error if it doesn't match.
You can expect 2 rpc calls for every query plus <= 1 query for each validator set
change. Going offline for a while allows you to verify multiple validator set changes
with one call. Cuz at 1 block/sec and 1000 tx/block, it just doesn't make sense
to run a full node just to get security
## Setup
Just initialize your client with the proper validator set as in the [README](README.md)
```
$ export BCHOME=~/.lightnode
$ basecli init --node tcp://<host>:<port> --chainid <chain>
```
## Running
```
$ basecli proxy --serve tcp://localhost:7890
...
curl localhost:7890/status
curl localhost:7890/block\?height=20
```
You can even subscribe to events over websockets and they are all verified
before passing them though. Though if you want every block, you might as
well run a full (nonvalidating) node.
## Seeds
Every time the validator set changes, the light node verifies if it is legal,
and then creates a seed at that point. These "seeds" are verified checkpoints
that we can trace any proof back to, starting with one on `init`.
To make sure you are based on the most recent header, you can run:
```
basecli seeds update
basecli seeds show
```
## Feedback
This is the first release of basecli and the light-weight proxy. It is secure, but
may not be useful for your workflow. Please try it out and open github issues
for any enhancements or bugs you find. I am aiming to make this a very useful
tool by tendermint 0.11, for which I need community feedback.

63
cmd/basecli/README.md Normal file
View File

@ -0,0 +1,63 @@
# Basic run through of using basecli....
To keep things clear, let's have two shells...
`$` is for basecoin (server), `%` is for basecli (client)
## Set up a clean basecoin, but don't start the chain
```
$ export BCHOME=~/.demoserve
$ basecoin init
```
## Set up your basecli with a new key
```
% export BCHOME=~/.democli
% basecli keys new demo
% basecli keys get demo -o json
```
And set up a few more keys for fun...
```
% basecli keys new buddy
% basecli keys list
% ME=`basecli keys get demo -o json | jq .address | tr -d '"'`
% YOU=`basecli keys get buddy -o json | jq .address | tr -d '"'`
```
## Update genesis so you are rich, and start
```
$ vi $BCHOME/genesis.json
-> cut/paste your pubkey from the results above
$ basecoin start
```
## Connect your basecli the first time
```
% basecli init --chainid test_chain_id --node tcp://localhost:46657
```
## Check your balances...
```
% basecli proof state get --app=account --key=$ME
% basecli proof state get --app=account --key=$YOU
```
## Send the money
```
% basecli tx send --name demo --amount 1000mycoin --sequence 1 --to $YOU
-> copy hash to HASH
% basecli proof tx get --key $HASH
% basecli proof tx get --key $HASH --app base
% basecli proof state get --key $YOU --app account
```

View File

@ -0,0 +1,232 @@
package commands
import (
"encoding/hex"
"encoding/json"
"github.com/pkg/errors"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
lightclient "github.com/tendermint/light-client"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/light-client/proofs"
btypes "github.com/tendermint/basecoin/types"
)
type AccountPresenter struct{}
func (_ AccountPresenter) MakeKey(str string) ([]byte, error) {
res, err := hex.DecodeString(str)
if err == nil {
res = btypes.AccountKey(res)
}
return res, err
}
func (_ AccountPresenter) ParseData(raw []byte) (interface{}, error) {
var acc *btypes.Account
err := wire.ReadBinaryBytes(raw, &acc)
return acc, err
}
type BaseTxPresenter struct {
proofs.RawPresenter // this handles MakeKey as hex bytes
}
func (_ BaseTxPresenter) ParseData(raw []byte) (interface{}, error) {
var tx btypes.TxS
err := wire.ReadBinaryBytes(raw, &tx)
return tx, err
}
/******** SendTx *********/
type SendTxMaker struct{}
func (m SendTxMaker) MakeReader() (lightclient.TxReader, error) {
chainID := viper.GetString(commands.ChainFlag)
return SendTxReader{ChainID: chainID}, nil
}
type SendFlags struct {
To string
Amount string
Fee string
Gas int64
Sequence int
}
func (m SendTxMaker) Flags() (*flag.FlagSet, interface{}) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.String("to", "", "Destination address for the bits")
fs.String("amount", "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.String("fee", "", "Coins for the transaction fee of the format <amt><coin>")
fs.Int64("gas", 0, "Amount of gas for this transaction")
fs.Int("sequence", -1, "Sequence number for this transaction")
return fs, &SendFlags{}
}
// SendTXReader allows us to create SendTx
type SendTxReader struct {
ChainID string
}
func (t SendTxReader) ReadTxJSON(data []byte, pk crypto.PubKey) (interface{}, error) {
// TODO: use pk info to help construct data
var tx btypes.SendTx
err := json.Unmarshal(data, &tx)
send := SendTx{
chainID: t.ChainID,
Tx: &tx,
}
return &send, errors.Wrap(err, "parse sendtx")
}
func (t SendTxReader) ReadTxFlags(flags interface{}, pk crypto.PubKey) (interface{}, error) {
data := flags.(*SendFlags)
// parse to and from addresses
to, err := hex.DecodeString(StripHex(data.To))
if err != nil {
return nil, errors.Errorf("To address is invalid hex: %v\n", err)
}
//parse the fee and amounts into coin types
feeCoin, err := btypes.ParseCoin(data.Fee)
if err != nil {
return nil, err
}
amountCoins, err := btypes.ParseCoins(data.Amount)
if err != nil {
return nil, err
}
// get addr if available
var addr []byte
if !pk.Empty() {
addr = pk.Address()
}
// craft the tx
input := btypes.TxInput{
Address: addr,
Coins: amountCoins,
Sequence: data.Sequence,
}
if data.Sequence == 1 {
input.PubKey = pk
}
output := btypes.TxOutput{
Address: to,
Coins: amountCoins,
}
tx := btypes.SendTx{
Gas: data.Gas,
Fee: feeCoin,
Inputs: []btypes.TxInput{input},
Outputs: []btypes.TxOutput{output},
}
// wrap it in the proper signer thing...
send := SendTx{
chainID: t.ChainID,
Tx: &tx,
}
return &send, nil
}
/******** AppTx *********/
type AppFlags struct {
Fee string
Gas int64
Amount string
Sequence int
}
func AppFlagSet() (*flag.FlagSet, AppFlags) {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.String("amount", "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.String("fee", "", "Coins for the transaction fee of the format <amt><coin>")
fs.Int64("gas", 0, "Amount of gas for this transaction")
fs.Int("sequence", -1, "Sequence number for this transaction")
return fs, AppFlags{}
}
// AppTxReader allows us to create AppTx
type AppTxReader struct {
ChainID string
}
func (t AppTxReader) ReadTxJSON(data []byte, pk crypto.PubKey) (interface{}, error) {
return nil, errors.New("Not implemented...")
}
func (t AppTxReader) ReadTxFlags(data *AppFlags, app string, appData []byte, pk crypto.PubKey) (interface{}, error) {
//parse the fee and amounts into coin types
feeCoin, err := btypes.ParseCoin(data.Fee)
if err != nil {
return nil, err
}
amountCoins, err := btypes.ParseCoins(data.Amount)
if err != nil {
return nil, err
}
// get addr if available
var addr []byte
if !pk.Empty() {
addr = pk.Address()
}
// craft the tx
input := btypes.TxInput{
Address: addr,
Coins: amountCoins,
Sequence: data.Sequence,
}
if data.Sequence == 1 {
input.PubKey = pk
}
tx := btypes.AppTx{
Gas: data.Gas,
Fee: feeCoin,
Input: input,
Name: app,
Data: appData,
}
// wrap it in the proper signer thing...
send := AppTx{
chainID: t.ChainID,
Tx: &tx,
}
return &send, nil
}
/** TODO copied from basecoin cli - put in common somewhere? **/
// 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
}

View File

@ -0,0 +1,59 @@
package commands
import (
"github.com/pkg/errors"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
wire "github.com/tendermint/go-wire"
bc "github.com/tendermint/basecoin/types"
)
type AppTx struct {
chainID string
signers []crypto.PubKey
Tx *bc.AppTx
}
var _ keys.Signable = &AppTx{}
// SignBytes returned the unsigned bytes, needing a signature
func (s *AppTx) SignBytes() []byte {
return s.Tx.SignBytes(s.chainID)
}
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *AppTx) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
if len(s.signers) > 0 {
return errors.New("AppTx already signed")
}
s.Tx.SetSignature(sig)
s.signers = []crypto.PubKey{pubkey}
return nil
}
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
func (s *AppTx) Signers() ([]crypto.PubKey, error) {
if len(s.signers) == 0 {
return nil, errors.New("No signatures on AppTx")
}
return s.signers, nil
}
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
func (s *AppTx) TxBytes() ([]byte, error) {
// TODO: verify it is signed
// Code and comment from: basecoin/cmd/commands/tx.go
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := wire.BinaryBytes(bc.TxS{s.Tx})
return txBytes, nil
}

View File

@ -0,0 +1,60 @@
package commands
import (
"github.com/pkg/errors"
bc "github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
keys "github.com/tendermint/go-crypto/keys"
wire "github.com/tendermint/go-wire"
)
type SendTx struct {
chainID string
signers []crypto.PubKey
Tx *bc.SendTx
}
var _ keys.Signable = &SendTx{}
// SignBytes returned the unsigned bytes, needing a signature
func (s *SendTx) SignBytes() []byte {
return s.Tx.SignBytes(s.chainID)
}
// Sign will add a signature and pubkey.
//
// Depending on the Signable, one may be able to call this multiple times for multisig
// Returns error if called with invalid data or too many times
func (s *SendTx) Sign(pubkey crypto.PubKey, sig crypto.Signature) error {
addr := pubkey.Address()
set := s.Tx.SetSignature(addr, sig)
if !set {
return errors.Errorf("Cannot add signature for address %X", addr)
}
s.signers = append(s.signers, pubkey)
return nil
}
// Signers will return the public key(s) that signed if the signature
// is valid, or an error if there is any issue with the signature,
// including if there are no signatures
func (s *SendTx) Signers() ([]crypto.PubKey, error) {
if len(s.signers) == 0 {
return nil, errors.New("No signatures on SendTx")
}
return s.signers, nil
}
// TxBytes returns the transaction data as well as all signatures
// It should return an error if Sign was never called
func (s *SendTx) TxBytes() ([]byte, error) {
// TODO: verify it is signed
// Code and comment from: basecoin/cmd/commands/tx.go
// Don't you hate having to do this?
// How many times have I lost an hour over this trick?!
txBytes := wire.BinaryBytes(struct {
bc.Tx `json:"unwrap"`
}{s.Tx})
return txBytes, nil
}

View File

@ -0,0 +1,85 @@
package counter
import (
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
crypto "github.com/tendermint/go-crypto"
wire "github.com/tendermint/go-wire"
lightclient "github.com/tendermint/light-client"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/light-client/commands/txs"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
"github.com/tendermint/basecoin/plugins/counter"
btypes "github.com/tendermint/basecoin/types"
)
type CounterPresenter struct{}
func (_ CounterPresenter) MakeKey(str string) ([]byte, error) {
key := counter.New().StateKey()
return key, nil
}
func (_ CounterPresenter) ParseData(raw []byte) (interface{}, error) {
var cp counter.CounterPluginState
err := wire.ReadBinaryBytes(raw, &cp)
return cp, err
}
/**** build out the tx ****/
var (
_ txs.ReaderMaker = CounterTxMaker{}
_ lightclient.TxReader = CounterTxReader{}
)
type CounterTxMaker struct{}
func (m CounterTxMaker) MakeReader() (lightclient.TxReader, error) {
chainID := viper.GetString(commands.ChainFlag)
return CounterTxReader{bcmd.AppTxReader{ChainID: chainID}}, nil
}
// define flags
type CounterFlags struct {
bcmd.AppFlags `mapstructure:",squash"`
Valid bool
CountFee string
}
func (m CounterTxMaker) Flags() (*flag.FlagSet, interface{}) {
fs, app := bcmd.AppFlagSet()
fs.String("countfee", "", "Coins to send in the format <amt><coin>,<amt><coin>...")
fs.Bool("valid", false, "Is count valid?")
return fs, &CounterFlags{AppFlags: app}
}
// parse flags
type CounterTxReader struct {
App bcmd.AppTxReader
}
func (t CounterTxReader) ReadTxJSON(data []byte, pk crypto.PubKey) (interface{}, error) {
// TODO: something. maybe?
return t.App.ReadTxJSON(data, pk)
}
func (t CounterTxReader) ReadTxFlags(flags interface{}, pk crypto.PubKey) (interface{}, error) {
data := flags.(*CounterFlags)
countFee, err := btypes.ParseCoins(data.CountFee)
if err != nil {
return nil, err
}
ctx := counter.CounterTx{
Valid: viper.GetBool("valid"),
Fee: countFee,
}
txBytes := wire.BinaryBytes(ctx)
return t.App.ReadTxFlags(&data.AppFlags, counter.New().Name(), txBytes, pk)
}

55
cmd/basecli/main.go Normal file
View File

@ -0,0 +1,55 @@
package main
import (
"os"
"github.com/spf13/cobra"
keycmd "github.com/tendermint/go-crypto/cmd"
"github.com/tendermint/light-client/commands"
"github.com/tendermint/light-client/commands/proofs"
"github.com/tendermint/light-client/commands/proxy"
"github.com/tendermint/light-client/commands/seeds"
"github.com/tendermint/light-client/commands/txs"
"github.com/tendermint/tmlibs/cli"
bcmd "github.com/tendermint/basecoin/cmd/basecli/commands"
bcount "github.com/tendermint/basecoin/cmd/basecli/counter"
)
// BaseCli represents the base command when called without any subcommands
var BaseCli = &cobra.Command{
Use: "basecli",
Short: "Light client for tendermint",
Long: `Basecli is an version of tmcli including custom logic to
present a nice (not raw hex) interface to the basecoin blockchain structure.
This is a useful tool, but also serves to demonstrate how one can configure
tmcli to work for any custom abci app.
`,
}
func main() {
commands.AddBasicFlags(BaseCli)
//initialize proofs and txs
proofs.StatePresenters.Register("account", bcmd.AccountPresenter{})
proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{})
proofs.StatePresenters.Register("counter", bcount.CounterPresenter{})
txs.Register("send", bcmd.SendTxMaker{})
txs.Register("counter", bcount.CounterTxMaker{})
// set up the various commands to use
BaseCli.AddCommand(
keycmd.RootCmd,
commands.InitCmd,
seeds.RootCmd,
proofs.RootCmd,
txs.RootCmd,
proxy.RootCmd,
)
cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli"))
cmd.Execute()
}

View File

@ -1,9 +1,12 @@
package main
import (
"os"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
)
func main() {
@ -15,6 +18,7 @@ func main() {
RootCmd.AddCommand(
commands.InitCmd,
commands.StartCmd,
commands.RelayCmd,
commands.TxCmd,
commands.QueryCmd,
commands.KeyCmd,
@ -25,5 +29,8 @@ func main() {
commands.VersionCmd,
)
commands.ExecuteWithDebug(RootCmd)
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin"))
if err := cmd.Execute(); err != nil {
os.Exit(1)
}
}

View File

@ -10,8 +10,8 @@ import (
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
tmtypes "github.com/tendermint/tendermint/types"
)
@ -196,13 +196,18 @@ func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error {
return err
}
var payload ibc.Payload
if err := wire.ReadBinaryBytes(payloadBytes, &payload); err != nil {
return err
}
ibcTx := ibc.IBCPacketCreateTx{
Packet: ibc.Packet{
SrcChainID: fromChain,
DstChainID: toChain,
Sequence: sequence,
Type: packetType,
Payload: payloadBytes,
Payload: payload,
},
}
@ -229,7 +234,7 @@ func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error {
}
var packet ibc.Packet
proof := new(merkle.IAVLProof)
proof := new(iavl.IAVLProof)
err = wire.ReadBinaryBytes(packetBytes, &packet)
if err != nil {

View File

@ -6,8 +6,6 @@ import (
"path"
"github.com/spf13/cobra"
cmn "github.com/tendermint/go-common"
)
//commands
@ -33,15 +31,17 @@ func setupFile(path, data string, perm os.FileMode) (int, error) {
}
func initCmd(cmd *cobra.Command, args []string) error {
rootDir := BasecoinRoot("")
cmn.EnsureDir(rootDir, 0777)
// this will ensure that config.toml is there if not yet created, and create dir
cfg, err := getTendermintConfig()
if err != nil {
return err
}
// initalize basecoin
genesisFile := path.Join(rootDir, "genesis.json")
privValFile := path.Join(rootDir, "priv_validator.json")
key1File := path.Join(rootDir, "key.json")
key2File := path.Join(rootDir, "key2.json")
genesisFile := cfg.GenesisFile()
privValFile := cfg.PrivValidatorFile()
key1File := path.Join(cfg.RootDir, "key.json")
key2File := path.Join(cfg.RootDir, "key2.json")
mod1, err := setupFile(genesisFile, GenesisJSON, 0644)
if err != nil {
@ -61,29 +61,29 @@ func initCmd(cmd *cobra.Command, args []string) error {
}
if (mod1 + mod2 + mod3 + mod4) > 0 {
log.Notice("Initialized Basecoin", "genesis", genesisFile, "key", key1File)
logger.Info("Initialized Basecoin", "genesis", genesisFile, "key", key1File)
} else {
log.Notice("Already initialized", "priv_validator", privValFile)
logger.Info("Already initialized", "priv_validator", privValFile)
}
return nil
}
var PrivValJSON = `{
"address": "7A956FADD20D3A5B2375042B2959F8AB172A058F",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"D07ABE82A8B15559A983B2DB5D4842B2B6E4D6AF58B080005662F424F17D68C17B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
],
"pub_key": [
1,
"7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
]
"address": "7A956FADD20D3A5B2375042B2959F8AB172A058F",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": {
"type": "ed25519",
"data": "D07ABE82A8B15559A983B2DB5D4842B2B6E4D6AF58B080005662F424F17D68C17B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
},
"pub_key": {
"type": "ed25519",
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
}
}`
var GenesisJSON = `{
@ -94,10 +94,10 @@ var GenesisJSON = `{
{
"amount": 10,
"name": "",
"pub_key": [
1,
"7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
]
"pub_key": {
"type": "ed25519",
"data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30"
}
}
],
"app_options": {
@ -117,25 +117,25 @@ var GenesisJSON = `{
}`
var Key1JSON = `{
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
"priv_key": {
"type": "ed25519",
"data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
},
"pub_key": {
"type": "ed25519",
"data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
}
"address": "1B1BE55F969F54064628A63B9559E7C21C925165",
"priv_key": {
"type": "ed25519",
"data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
},
"pub_key": {
"type": "ed25519",
"data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"
}
}`
var Key2JSON = `{
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
"priv_key": {
"type": "ed25519",
"data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
},
"pub_key": {
"type": "ed25519",
"data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
}
"address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090",
"priv_key": {
"type": "ed25519",
"data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
},
"pub_key": {
"type": "ed25519",
"data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8"
}
}`

View File

@ -10,8 +10,10 @@ import (
//"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/cli"
)
//commands
@ -62,9 +64,9 @@ func (a *Address) UnmarshalJSON(addrHex []byte) error {
}
type Key struct {
Address Address `json:"address"`
PubKey crypto.PubKeyS `json:"pub_key"`
PrivKey crypto.PrivKeyS `json:"priv_key"`
Address Address `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
PrivKey crypto.PrivKey `json:"priv_key"`
}
// Implements Signer
@ -75,18 +77,25 @@ func (k *Key) Sign(msg []byte) crypto.Signature {
// Generates a new validator with private key.
func genKey() *Key {
privKey := crypto.GenPrivKeyEd25519()
addrBytes := privKey.PubKey().Address()
pubKey := privKey.PubKey()
addrBytes := pubKey.Address()
var addr Address
copy(addr[:], addrBytes)
return &Key{
Address: addr,
PubKey: crypto.PubKeyS{privKey.PubKey()},
PrivKey: crypto.PrivKeyS{privKey},
PubKey: pubKey,
PrivKey: privKey.Wrap(),
}
}
func LoadKey(keyFile string) (*Key, error) {
filePath := path.Join(BasecoinRoot(""), keyFile)
filePath := keyFile
if !strings.HasPrefix(keyFile, "/") && !strings.HasPrefix(keyFile, ".") {
rootDir := viper.GetString(cli.HomeFlag)
filePath = path.Join(rootDir, keyFile)
}
keyJSONBytes, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, err

View File

@ -1,7 +0,0 @@
package commands
import (
"github.com/tendermint/go-logger"
)
var log = logger.New("module", "commands")

View File

@ -8,8 +8,9 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types"
)
@ -120,7 +121,8 @@ func accountCmd(cmd *cobra.Command, args []string) error {
return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err)
}
acc, err := getAcc(nodeFlag, addr)
httpClient := client.NewHTTP(nodeFlag, "/websocket")
acc, err := getAccWithClient(httpClient, addr)
if err != nil {
return err
}
@ -202,7 +204,7 @@ func verifyCmd(cmd *cobra.Command, args []string) error {
return errors.Errorf("Proof (%v) is invalid hex: %v\n", proofFlag, err)
}
proof, err := merkle.ReadProof(proofBytes)
proof, err := iavl.ReadProof(proofBytes)
if err != nil {
return errors.Errorf("Error unmarshalling proof: %v\n", err)
}

247
cmd/commands/relay.go Normal file
View File

@ -0,0 +1,247 @@
package commands
import (
"fmt"
"strconv"
"time"
"github.com/pkg/errors"
"github.com/spf13/cobra"
// "github.com/spf13/viper"
// "github.com/tendermint/tmlibs/cli"
// "github.com/tendermint/tmlibs/log"
"github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
"github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types"
)
var RelayCmd = &cobra.Command{
Use: "relay",
Short: "Start basecoin relayer to relay IBC packets between chains",
RunE: relayCmd,
}
//flags
var (
chain1AddrFlag string
chain2AddrFlag string
chain1IDFlag string
chain2IDFlag string
fromFileFlag string
)
func init() {
flags := []Flag2Register{
{&chain1AddrFlag, "chain1-addr", "tcp://localhost:46657", "Node address for chain1"},
{&chain2AddrFlag, "chain2-addr", "tcp://localhost:36657", "Node address for chain2"},
{&chain1IDFlag, "chain1-id", "test_chain_1", "ChainID for chain1"},
{&chain2IDFlag, "chain2-id", "test_chain_2", "ChainID for chain2"},
{&fromFileFlag, "from", "key.json", "Path to a private key to sign the transaction"},
}
RegisterFlags(RelayCmd, flags)
}
func loop(addr1, addr2, id1, id2 string) {
nextSeq := 0
// load the priv key
privKey, err := LoadKey(fromFlag)
if err != nil {
logger.Error(err.Error())
cmn.PanicCrisis(err.Error())
}
// relay from chain1 to chain2
thisRelayer := newRelayer(privKey, id2, addr2)
logger.Info(fmt.Sprintf("Relaying from chain %v on %v to chain %v on %v", id1, addr1, id2, addr2))
httpClient := client.NewHTTP(addr1, "/websocket")
OUTER:
for {
time.Sleep(time.Second)
// get the latest ibc packet sequence number
key := fmt.Sprintf("ibc,egress,%v,%v", id1, id2)
query, err := queryWithClient(httpClient, []byte(key))
if err != nil {
logger.Error("Error querying for latest sequence", "key", key, "error", err.Error())
continue OUTER
}
if len(query.Value) == 0 {
// nothing yet
continue OUTER
}
seq, err := strconv.ParseUint(string(query.Value), 10, 64)
if err != nil {
logger.Error("Error parsing sequence number from query", "query.Value", query.Value, "error", err.Error())
continue OUTER
}
seq -= 1 // seq is the packet count. -1 because 0-indexed
if nextSeq <= int(seq) {
logger.Info("Got new packets", "last-sequence", nextSeq-1, "new-sequence", seq)
}
// get all packets since the last one we relayed
for ; nextSeq <= int(seq); nextSeq++ {
key := fmt.Sprintf("ibc,egress,%v,%v,%d", id1, id2, nextSeq)
query, err := queryWithClient(httpClient, []byte(key))
if err != nil {
logger.Error("Error querying for packet", "seqeuence", nextSeq, "key", key, "error", err.Error())
continue OUTER
}
var packet ibc.Packet
err = wire.ReadBinaryBytes(query.Value, &packet)
if err != nil {
logger.Error("Error unmarshalling packet", "key", key, "query.Value", query.Value, "error", err.Error())
continue OUTER
}
proof := new(iavl.IAVLProof)
err = wire.ReadBinaryBytes(query.Proof, &proof)
if err != nil {
logger.Error("Error unmarshalling proof", "query.Proof", query.Proof, "error", err.Error())
continue OUTER
}
// query.Height is actually for the next block,
// so wait a block before we fetch the header & commit
if err := waitForBlock(httpClient); err != nil {
logger.Error("Error waiting for a block", "addr", addr1, "error", err.Error())
continue OUTER
}
// get the header and commit from the height the query was done at
res, err := httpClient.Commit(int(query.Height))
if err != nil {
logger.Error("Error fetching header and commits", "height", query.Height, "error", err.Error())
continue OUTER
}
// update the chain state on the other chain
updateTx := ibc.IBCUpdateChainTx{
Header: *res.Header,
Commit: *res.Commit,
}
logger.Info("Updating chain", "src-chain", id1, "height", res.Header.Height, "appHash", res.Header.AppHash)
if err := thisRelayer.appTx(updateTx); err != nil {
logger.Error("Error creating/sending IBCUpdateChainTx", "error", err.Error())
continue OUTER
}
// relay the packet and proof
logger.Info("Relaying packet", "src-chain", id1, "height", query.Height, "sequence", nextSeq)
postTx := ibc.IBCPacketPostTx{
FromChainID: id1,
FromChainHeight: query.Height,
Packet: packet,
Proof: proof,
}
if err := thisRelayer.appTx(postTx); err != nil {
logger.Error("Error creating/sending IBCPacketPostTx", "error", err.Error())
// dont `continue OUTER` here. the error might be eg. Already exists
// TODO: catch this programmatically ?
}
}
}
}
type relayer struct {
privKey *Key
chainID string
nodeAddr string
client *client.HTTP
}
func newRelayer(privKey *Key, chainID, nodeAddr string) *relayer {
httpClient := client.NewHTTP(nodeAddr, "/websocket")
return &relayer{
privKey: privKey,
chainID: chainID,
nodeAddr: nodeAddr,
client: httpClient,
}
}
func (r *relayer) appTx(ibcTx ibc.IBCTx) error {
acc, err := getAccWithClient(r.client, r.privKey.Address[:])
if err != nil {
return err
}
sequence := acc.Sequence + 1
data := []byte(wire.BinaryBytes(struct {
ibc.IBCTx `json:"unwrap"`
}{ibcTx}))
smallCoins := types.Coin{"mycoin", 1}
input := types.NewTxInput(r.privKey.PubKey, types.Coins{smallCoins}, sequence)
tx := &types.AppTx{
Gas: 0,
Fee: smallCoins,
Name: "IBC",
Input: input,
Data: data,
}
tx.Input.Signature = r.privKey.Sign(tx.SignBytes(r.chainID))
txBytes := []byte(wire.BinaryBytes(struct {
types.Tx `json:"unwrap"`
}{tx}))
data, log, err := broadcastTxWithClient(r.client, txBytes)
if err != nil {
return err
}
_, _ = data, log
return nil
}
// broadcast the transaction to tendermint
func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, string, error) {
res, err := httpClient.BroadcastTxCommit(tx)
if err != nil {
return nil, "", errors.Errorf("Error on broadcast tx: %v", err)
}
if !res.CheckTx.Code.IsOK() {
r := res.CheckTx
return nil, "", errors.Errorf("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.Errorf("BroadcastTxCommit got non-zero exit code: %v. %X; %s", r.Code, r.Data, r.Log)
}
return res.DeliverTx.Data, res.DeliverTx.Log, nil
}
func relayCmd(cmd *cobra.Command, args []string) error {
go loop(chain1AddrFlag, chain2AddrFlag, chain1IDFlag, chain2IDFlag)
go loop(chain2AddrFlag, chain1AddrFlag, chain2IDFlag, chain1IDFlag)
cmn.TrapSignal(func() {
// TODO: Cleanup
})
return nil
}

View File

@ -4,7 +4,6 @@ import (
"github.com/spf13/cobra"
tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
tmcfg "github.com/tendermint/tendermint/config/tendermint"
)
var UnsafeResetAllCmd = &cobra.Command{
@ -14,8 +13,10 @@ var UnsafeResetAllCmd = &cobra.Command{
}
func unsafeResetAllCmd(cmd *cobra.Command, args []string) error {
basecoinDir := BasecoinRoot("")
tmConfig := tmcfg.GetConfig(basecoinDir)
tmcmd.ResetAll(tmConfig, log)
cfg, err := getTendermintConfig()
if err != nil {
return err
}
tmcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), logger)
return nil
}

11
cmd/commands/root.go Normal file
View File

@ -0,0 +1,11 @@
package commands
import (
"os"
"github.com/tendermint/tmlibs/log"
)
var (
logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main")
)

View File

@ -7,15 +7,18 @@ import (
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/abci/server"
cmn "github.com/tendermint/go-common"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/cli"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
tmcfg "github.com/tendermint/tendermint/config/tendermint"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/proxy"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app"
)
@ -49,12 +52,12 @@ func init() {
}
func startCmd(cmd *cobra.Command, args []string) error {
basecoinDir := BasecoinRoot("")
rootDir := viper.GetString(cli.HomeFlag)
// Connect to MerkleEyes
var eyesCli *eyes.Client
if eyesFlag == "local" {
eyesCli = eyes.NewLocalClient(path.Join(basecoinDir, "data", "merkleeyes.db"), EyesCacheSize)
eyesCli = eyes.NewLocalClient(path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize)
} else {
var err error
eyesCli, err = eyes.NewClient(eyesFlag)
@ -65,6 +68,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
// Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli)
basecoinApp.SetLogger(logger.With("module", "app"))
// register IBC plugn
basecoinApp.RegisterPlugin(NewIBCPlugin())
@ -78,7 +82,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
// else, assume it's been loaded
if basecoinApp.GetState().GetChainID() == "" {
// If genesis file exists, set key-value options
genesisFile := path.Join(basecoinDir, "genesis.json")
genesisFile := path.Join(rootDir, "genesis.json")
if _, err := os.Stat(genesisFile); err == nil {
err := basecoinApp.LoadGenesis(genesisFile)
if err != nil {
@ -91,23 +95,24 @@ func startCmd(cmd *cobra.Command, args []string) error {
chainID := basecoinApp.GetState().GetChainID()
if withoutTendermintFlag {
log.Notice("Starting Basecoin without Tendermint", "chain_id", chainID)
logger.Info("Starting Basecoin without Tendermint", "chain_id", chainID)
// run just the abci app/server
return startBasecoinABCI(basecoinApp)
} else {
log.Notice("Starting Basecoin with Tendermint", "chain_id", chainID)
logger.Info("Starting Basecoin with Tendermint", "chain_id", chainID)
// start the app with tendermint in-process
return startTendermint(basecoinDir, basecoinApp)
return startTendermint(rootDir, basecoinApp)
}
}
func startBasecoinABCI(basecoinApp *app.Basecoin) error {
// Start the ABCI listener
svr, err := server.NewServer(addrFlag, "socket", basecoinApp)
if err != nil {
return errors.Errorf("Error creating listener: %v\n", err)
}
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
// Wait forever
cmn.TrapSignal(func() {
@ -117,27 +122,41 @@ func startBasecoinABCI(basecoinApp *app.Basecoin) error {
return nil
}
func getTendermintConfig() (*config.Config, error) {
cfg := config.DefaultConfig()
err := viper.Unmarshal(cfg)
if err != nil {
return nil, err
}
cfg.SetRoot(cfg.RootDir)
config.EnsureRoot(cfg.RootDir)
return cfg, nil
}
func startTendermint(dir string, basecoinApp *app.Basecoin) error {
// Get configuration
tmConfig := tmcfg.GetConfig(dir)
// logger.SetLogLevel("notice") //config.GetString("log_level"))
// parseFlags(config, args[1:]) // Command line overrides
// Create & start tendermint node
privValidatorFile := tmConfig.GetString("priv_validator_file")
privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile)
n := node.NewNode(tmConfig, privValidator, proxy.NewLocalClientCreator(basecoinApp))
_, err := n.Start()
cfg, err := getTendermintConfig()
if err != nil {
return err
}
// Wait forever
cmn.TrapSignal(func() {
// Cleanup
n.Stop()
})
// TODO: parse the log level from the config properly (multi modules)
// but some tm code must be refactored for better usability
lvl, err := log.AllowLevel(cfg.LogLevel)
if err != nil {
return err
}
tmLogger := log.NewFilter(logger, lvl)
// Create & start tendermint node
privValidator := types.LoadOrGenPrivValidator(cfg.PrivValidatorFile(), tmLogger)
n := node.NewNode(cfg, privValidator, proxy.NewLocalClientCreator(basecoinApp), tmLogger.With("module", "node"))
_, err = n.Start()
if err != nil {
return err
}
// Trap signal, run forever.
n.RunForever()
return nil
}

View File

@ -3,16 +3,15 @@ package commands
import (
"encoding/hex"
"fmt"
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
client "github.com/tendermint/go-rpc/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/tendermint/tendermint/rpc/client"
)
//commands
@ -60,7 +59,7 @@ func init() {
{&fromFlag, "from", "key.json", "Path to a private key to sign the transaction"},
{&amountFlag, "amount", "", "Coins to send in transaction of the format <amt><coin>,<amt2><coin2>,... (eg: 1btc,2gold,5silver)"},
{&gasFlag, "gas", 0, "The amount of gas for the transaction"},
{&feeFlag, "fee", "", "Coins for the transaction fee of the format <amt><coin>"},
{&feeFlag, "fee", "0coin", "Coins for the transaction fee of the format <amt><coin>"},
{&seqFlag, "sequence", -1, "Sequence number for the account (-1 to autocalculate)"},
}
@ -83,12 +82,29 @@ func init() {
func sendTxCmd(cmd *cobra.Command, args []string) error {
var toHex string
var chainPrefix string
spl := strings.Split(toFlag, "/")
switch len(spl) {
case 1:
toHex = spl[0]
case 2:
chainPrefix = spl[0]
toHex = spl[1]
default:
return errors.Errorf("To address has too many slashes")
}
// convert destination address to bytes
to, err := hex.DecodeString(StripHex(toFlag))
to, err := hex.DecodeString(StripHex(toHex))
if err != nil {
return errors.Errorf("To address is invalid hex: %v\n", err)
}
if chainPrefix != "" {
to = []byte(chainPrefix + "/" + string(to))
}
// load the priv key
privKey, err := LoadKey(fromFlag)
if err != nil {
@ -123,7 +139,7 @@ func sendTxCmd(cmd *cobra.Command, args []string) error {
// sign that puppy
signBytes := tx.SignBytes(chainIDFlag)
tx.Inputs[0].Signature = crypto.SignatureS{privKey.Sign(signBytes)}
tx.Inputs[0].Signature = privKey.Sign(signBytes)
fmt.Println("Signed SendTx:")
fmt.Println(string(wire.JSONBytes(tx)))
@ -179,7 +195,7 @@ func AppTx(name string, data []byte) error {
Data: data,
}
tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainIDFlag))}
tx.Input.Signature = privKey.Sign(tx.SignBytes(chainIDFlag))
fmt.Println("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx)))
@ -194,23 +210,17 @@ func AppTx(name string, data []byte) error {
// broadcast the transaction to tendermint
func broadcastTx(tx types.Tx) ([]byte, string, error) {
tmResult := new(ctypes.TMResult)
uriClient := client.NewURIClient(txNodeFlag)
httpClient := client.NewHTTP(txNodeFlag, "/websocket")
// 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 := uriClient.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
res, err := httpClient.BroadcastTxCommit(txBytes)
if err != nil {
return nil, "", errors.Errorf("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
@ -228,12 +238,12 @@ func broadcastTx(tx types.Tx) ([]byte, string, error) {
// if the sequence flag is set, return it;
// else, fetch the account by querying the app and return the sequence number
func getSeq(address []byte) (int, error) {
if seqFlag >= 0 {
return seqFlag, nil
}
acc, err := getAcc(txNodeFlag, address)
httpClient := client.NewHTTP(txNodeFlag, "/websocket")
acc, err := getAccWithClient(httpClient, address)
if err != nil {
return 0, err
}

View File

@ -3,52 +3,20 @@ package commands
import (
"encoding/hex"
"fmt"
"os"
"path"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/tendermint/basecoin/state"
abci "github.com/tendermint/abci/types"
wire "github.com/tendermint/go-wire"
"github.com/tendermint/basecoin/types"
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/go-common"
client "github.com/tendermint/go-rpc/client"
wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types"
client "github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types"
)
//This variable can be overwritten by plugin applications
// if they require a different working directory
var DefaultHome = ".basecoin"
func BasecoinRoot(rootDir string) string {
if rootDir == "" {
rootDir = os.Getenv("BCHOME")
}
if rootDir == "" {
rootDir = path.Join(os.Getenv("HOME"), DefaultHome)
}
return rootDir
}
//Add debugging flag and execute the root command
func ExecuteWithDebug(RootCmd *cobra.Command) {
var debug bool
RootCmd.SilenceUsage = true
RootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enables stack trace error messages")
//note that Execute() prints the error if encountered, so no need to reprint the error,
// only if we want the full stack trace
if err := RootCmd.Execute(); err != nil && debug {
cmn.Exit(fmt.Sprintf("%+v\n", err))
}
}
//Quickly registering flags can be quickly achieved through using the utility functions
//RegisterFlags, and RegisterPersistentFlags. Ex:
// flags := []Flag2Register{
@ -130,31 +98,27 @@ func StripHex(s string) string {
return s
}
func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) {
uriClient := client.NewURIClient(tmAddr)
tmResult := new(ctypes.TMResult)
func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) {
httpClient := client.NewHTTP(tmAddr, "/websocket")
return queryWithClient(httpClient, key)
}
params := map[string]interface{}{
"path": "/key",
"data": key,
"prove": true,
}
_, err := uriClient.Call("abci_query", params, tmResult)
func queryWithClient(httpClient *client.HTTP, key []byte) (*abci.ResultQuery, error) {
res, err := httpClient.ABCIQuery("/key", key, true)
if err != nil {
return nil, errors.Errorf("Error calling /abci_query: %v", err)
}
res := (*tmResult).(*ctypes.ResultABCIQuery)
if !res.Response.Code.IsOK() {
return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)
if !res.Code.IsOK() {
return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Code, res.Log)
}
return &res.Response, nil
return res.ResultQuery, nil
}
// fetch the account by querying the app
func getAcc(tmAddr string, address []byte) (*types.Account, error) {
func getAccWithClient(httpClient *client.HTTP, address []byte) (*types.Account, error) {
key := state.AccountKey(address)
response, err := Query(tmAddr, key)
key := types.AccountKey(address)
response, err := queryWithClient(httpClient, key)
if err != nil {
return nil, err
}
@ -176,17 +140,33 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) {
}
func getHeaderAndCommit(tmAddr string, height int) (*tmtypes.Header, *tmtypes.Commit, error) {
tmResult := new(ctypes.TMResult)
uriClient := client.NewURIClient(tmAddr)
method := "commit"
_, err := uriClient.Call(method, map[string]interface{}{"height": height}, tmResult)
httpClient := client.NewHTTP(tmAddr, "/websocket")
res, err := httpClient.Commit(height)
if err != nil {
return nil, nil, errors.Errorf("Error on %s: %v", method, err)
return nil, nil, errors.Errorf("Error on commit: %v", err)
}
resCommit := (*tmResult).(*ctypes.ResultCommit)
header := resCommit.Header
commit := resCommit.Commit
header := res.Header
commit := res.Commit
return header, commit, nil
}
func waitForBlock(httpClient *client.HTTP) error {
res, err := httpClient.Status()
if err != nil {
return err
}
lastHeight := res.LatestBlockHeight
for {
res, err := httpClient.Status()
if err != nil {
return err
}
if res.LatestBlockHeight > lastHeight {
break
}
}
return nil
}

View File

@ -1,19 +1,22 @@
package main
import (
"os"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
)
func main() {
var RootCmd = &cobra.Command{
Use: "counter",
Short: "demo plugin for basecoin",
}
RootCmd.AddCommand(
commands.InitCmd,
commands.StartCmd,
commands.TxCmd,
commands.QueryCmd,
@ -21,8 +24,10 @@ func main() {
commands.VerifyCmd,
commands.BlockCmd,
commands.AccountCmd,
commands.QuickVersionCmd("0.1.0"),
commands.UnsafeResetAllCmd,
commands.VersionCmd,
)
commands.ExecuteWithDebug(RootCmd)
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin"))
cmd.Execute()
}

View File

@ -7,5 +7,5 @@ node_laddr = "tcp://0.0.0.0:46656"
seeds = ""
fast_sync = true
db_backend = "leveldb"
log_level = "notice"
log_level = "info"
rpc_laddr = "tcp://0.0.0.0:46657"

View File

@ -6,10 +6,10 @@
{
"amount": 10,
"name": "",
"pub_key": [
1,
"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
]
"pub_key": {
"type": "ed25519",
"data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
}
}
],
"app_options": {

View File

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

View File

@ -7,5 +7,5 @@ node_laddr = "tcp://0.0.0.0:46656"
seeds = ""
fast_sync = true
db_backend = "leveldb"
log_level = "notice"
log_level = "info"
rpc_laddr = "tcp://0.0.0.0:46657"

View File

@ -6,10 +6,10 @@
{
"amount": 10,
"name": "",
"pub_key": [
1,
"9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
]
"pub_key": {
"type": "ed25519",
"data": "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
}
}
],
"app_options": {

View File

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

View File

@ -4,7 +4,8 @@ set -e
cd $GOPATH/src/github.com/tendermint/basecoin/demo
LOG_DIR="."
TM_VERSION="v0.9.2"
TM_VERSION="develop"
#TM_VERSION="v0.10.0"
if [[ "$CIRCLECI" == "true" ]]; then
# set log dir
@ -23,6 +24,13 @@ fi
set -u
function ifExit() {
if [[ "$?" != 0 ]]; then
echo "FAIL"
exit 1
fi
}
function removeQuotes() {
temp="${1%\"}"
temp="${temp#\"}"
@ -41,7 +49,7 @@ function waitForNode() {
exit 1
fi
echo "...... still waiting on $addr"
sleep 1
sleep 1
curl -s $addr/status > /dev/null
ERR=$?
i=$((i+1))
@ -52,12 +60,12 @@ function waitForNode() {
function waitForBlock() {
addr=$1
b1=`curl -s $addr/status | jq .result[1].latest_block_height`
b1=`curl -s $addr/status | jq .result.latest_block_height`
b2=$b1
while [ "$b2" == "$b1" ]; do
echo "Waiting for node $addr to commit a block ..."
sleep 1
b2=`curl -s $addr/status | jq .result[1].latest_block_height`
b2=`curl -s $addr/status | jq .result.latest_block_height`
done
}
@ -83,12 +91,16 @@ echo ""
echo "... starting chains"
echo ""
# start the first node
TMROOT=$BCHOME1 tendermint node --skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log &
TMROOT=$BCHOME1 tendermint node --p2p.skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log &
ifExit
BCHOME=$BCHOME1 basecoin start --without-tendermint &> $LOG_DIR/chain1_basecoin.log &
ifExit
# start the second node
TMROOT=$BCHOME2 tendermint node --skip_upnp --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log &
TMROOT=$BCHOME2 tendermint node --p2p.skip_upnp --log_level=info --p2p.laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log &
ifExit
BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> $LOG_DIR/chain2_basecoin.log &
ifExit
echo ""
echo "... waiting for chains to start"
@ -99,25 +111,32 @@ waitForNode localhost:36657
# TODO: remove the sleep
# Without it we sometimes get "Account bytes are empty for address: 053BA0F19616AFF975C8756A2CBFF04F408B4D47"
sleep 3
sleep 3
echo "... registering chain1 on chain2"
echo ""
# register chain1 on chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json
ifExit
echo ""
echo "... creating egress packet on chain1"
echo ""
# create a packet on chain1 destined for chain2
PAYLOAD="DEADBEEF" #TODO
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 1
# send coins from chain1 to an address on chain2
# TODO: dont hardcode the address
basecoin tx send --amount 10mycoin $CHAIN_FLAGS1 --to $CHAIN_ID2/053BA0F19616AFF975C8756A2CBFF04F408B4D47
ifExit
# alternative way to create packets (for testing)
# basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 0
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)
# since we only sent one packet, the sequence number is 0
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,0)
ifExit
HEIGHT=$(echo $QUERY_RESULT | jq .height)
PACKET=$(echo $QUERY_RESULT | jq .value)
PROOF=$(echo $QUERY_RESULT | jq .proof)
@ -143,7 +162,8 @@ echo ""
echo "... querying for block data"
echo ""
# get the header and commit for the height
HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
ifExit
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
HEADER=$(removeQuotes $HEADER)
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
@ -158,18 +178,21 @@ echo "... updating state of chain1 on chain2"
echo ""
# update the state of chain1 on chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
ifExit
echo ""
echo "... posting packet from chain1 on chain2"
echo ""
# post the packet from chain1 to chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF
ifExit
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
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,0
ifExit
echo ""
echo "DONE!"

View File

@ -98,7 +98,7 @@ make install
make test
```
Great! Now when I run `tendermint` I have the newest of the new, the develop branch! But please not that this branch is not considered production ready and may have issues. This should only be done if you want to develop code for the future and run locally.
Great! Now when I run `tendermint` I have the newest of the new, the develop branch! But please note that this branch is not considered production ready and may have issues. This should only be done if you want to develop code for the future and run locally.
But wait, I want to mix and match. There is a bugfix in `go-p2p:persistent_peer` that I want to use with tendermint. How to compile this. I will show with a simple example, please update the repo and commit numbers for your usecase. Also, make sure these branches are compatible, so if `persistent_peer` is close to `master` it should work. But if it is 15 commits ahead, you will probably need the `develop` branch of tendermint to compile with it. But I assume you know your way around git and can figure that out.
@ -129,4 +129,3 @@ Great, now you just compiled the master branch of tendermint along with the bugf
Okay, that's it, with this info you should be able to follow along and
trouble-shoot any issues you have with the rest of the guide.

View File

@ -111,7 +111,7 @@ type Coin struct {
Accounts are serialized and stored in a Merkle tree under the key `base/a/<address>`, where `<address>` is the address of the account.
Typically, the address of the account is the 20-byte `RIPEMD160` hash of the public key, but other formats are acceptable as well,
as defined in the [tendermint crypto library](https://github.com/tendermint/go-crypto).
as defined in the [Tendermint crypto library](https://github.com/tendermint/go-crypto).
The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle).
## Transactions
@ -150,8 +150,8 @@ 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
In Basecoin, 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...

View File

@ -144,7 +144,7 @@ to whatever your plugin tool is going to be called.
Next is the `cmd.go`. This is where we extend the tool with any new commands and flags we need to send transactions to our plugin.
Note the `init()` function, where we register a new transaction subcommand with `RegisterTxSubcommand`,
and where we load the plugin into the basecoin app with `RegisterStartPlugin`.
and where we load the plugin into the Basecoin app with `RegisterStartPlugin`.
Finally is the `plugin.go`, where we provide an implementation of the `Plugin` interface.
The most important part of the implementation is the `RunTx` method, which determines the meaning of the data

View File

@ -1,7 +1,7 @@
# The Basecoin Tool
In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guides/basecoin-basics)
and [how to implement a plugin](/docs/guides/example-plugin).
In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guide/basecoin-basics.md)
and [how to implement a plugin](/docs/guide/basecoin-plugins.md).
In this tutorial, we provide more details on using the `basecoin` tool.
# Data Directory
@ -14,7 +14,7 @@ basecoin init
basecoin start
```
or
or
```
BCHOME=~/.my_basecoin_data basecoin init
@ -33,7 +33,7 @@ basecoin init
This will create a single `genesis.json` file in `~/.basecoin` with the information for both Basecoin and Tendermint.
Now, In one window, run
Now, In one window, run
```
basecoin start --without-tendermint
@ -147,7 +147,7 @@ basecoin unsafe_reset_all
Any required plugin initialization should be constructed using `SetOption` on genesis.
When starting a new chain for the first time, `SetOption` will be called for each item the genesis file.
Within genesis.json file entries are made in the format: `"<plugin>/<key>", "<value>"`, where `<plugin>` is the plugin name,
and `<key>` and `<value>` are the strings passed into the plugin SetOption function.
This function is intended to be used to set plugin specific information such
and `<key>` and `<value>` are the strings passed into the plugin SetOption function.
This function is intended to be used to set plugin specific information such
as the plugin state.

View File

@ -24,8 +24,6 @@ var (
//Called during CLI initialization
func init() {
commands.DefaultHome = ".basecoin-example-plugin"
//Set the Plugin Flags
ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid")

View File

@ -1,9 +1,12 @@
package main
import (
"os"
"github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
)
func main() {
@ -27,6 +30,6 @@ func main() {
commands.UnsafeResetAllCmd,
)
//Run the root command
commands.ExecuteWithDebug(RootCmd)
cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin-example-plugin"))
cmd.Execute()
}

178
glide.lock generated
View File

@ -1,39 +1,92 @@
hash: c6e5febc35b5fd1003066820defb8a089db048b407239dad9faf44553fdc15e8
updated: 2017-04-21T12:55:42.7004558-04:00
hash: 9d06ae13959cbb2835f5ae400a4b65e4bc329a567c949aec4aeab318c271da39
updated: 2017-05-24T15:11:32.643553723+02:00
imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd
version: 4b348c1d33373d672edd83fc576892d0e46686d2
version: b8df516b4b267acf2de46be593a9d948d1d2c420
subpackages:
- btcec
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/BurntSushi/toml
version: b26d9c308763d68093482582cea63d69be07a0f0
- name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4
- name: github.com/fsnotify/fsnotify
version: 4da3e2cfbabc9f751898f250b49f2439785783a1
- name: github.com/go-kit/kit
version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8
subpackages:
- log
- log/level
- log/term
- name: github.com/go-logfmt/logfmt
version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5
- name: github.com/go-playground/locales
version: 1e5f1161c6416a5ff48840eb8724a394e48cc534
subpackages:
- currency
- name: github.com/go-playground/universal-translator
version: 71201497bace774495daed26a3874fd339e0b538
- name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef
version: 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8
subpackages:
- proto
- ptypes/any
- name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9
- name: github.com/gorilla/context
version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42
- name: github.com/gorilla/handlers
version: 3a5767ca75ece5f7f1440b1d16975247f8d8b221
- name: github.com/gorilla/mux
version: bcd8bc72b08df0f70df986b97f95590779502d31
- name: github.com/gorilla/websocket
version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13
version: a91eba7f97777409bc2c443f5534d41dd20c5720
- name: github.com/hashicorp/hcl
version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca
subpackages:
- hcl/ast
- hcl/parser
- hcl/scanner
- hcl/strconv
- hcl/token
- json/parser
- json/scanner
- json/token
- name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/mattn/go-colorable
version: ded68f7a9561c023e790de24279db7ebf473ea80
- name: github.com/mattn/go-isatty
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
- name: github.com/kr/logfmt
version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/magiconair/properties
version: 51463bfca2576e06c62a8504b5c0f06d61312647
- name: github.com/mitchellh/mapstructure
version: cc8532a8e9a55ea36402aa21efdf403a60d34096
- name: github.com/pelletier/go-buffruneio
version: c37440a7cf42ac63b919c752ca73a85067e05992
- name: github.com/pelletier/go-toml
version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a
- name: github.com/pkg/errors
version: ff09b135c25aae272398c51a07235b90a75aa4f0
- name: github.com/spf13/afero
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
subpackages:
- mem
- name: github.com/spf13/cast
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
- name: github.com/spf13/cobra
version: 10f6b9d7e1631a54ad07c5c0fb71c28a1abfd3c2
version: 3454e0e28e69c1b8effa6b5123c8e4185e20d696
- name: github.com/spf13/jwalterweatherman
version: 8f07c835e5cc1450c082fe3a439cf87b0cbb2d99
- name: github.com/spf13/pflag
version: 2300d0f8576fe575f71aaa5b9bbe4e1b0dc2eb51
version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2
- name: github.com/syndtr/goleveldb
version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4
subpackages:
@ -50,7 +103,7 @@ imports:
- leveldb/table
- leveldb/util
- name: github.com/tendermint/abci
version: 56e13d87f4e3ec1ea756957d6b23caa6ebcf0998
version: 5dabeffb35c027d7087a12149685daa68989168b
subpackages:
- client
- example/dummy
@ -61,72 +114,80 @@ imports:
subpackages:
- edwards25519
- extra25519
- name: github.com/tendermint/go-autofile
version: 48b17de82914e1ec2f134ce823ba426337d2c518
- name: github.com/tendermint/go-clist
version: 3baa390bbaf7634251c42ad69a8682e7e3990552
- name: github.com/tendermint/go-common
version: f9e3db037330c8a8d61d3966de8473eaf01154fa
- name: github.com/tendermint/go-config
version: 620dcbbd7d587cf3599dedbf329b64311b0c307a
- name: github.com/tendermint/go-crypto
version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da
- name: github.com/tendermint/go-data
version: e7fcc6d081ec8518912fcdc103188275f83a3ee5
- name: github.com/tendermint/go-db
version: 9643f60bc2578693844aacf380a7c32e4c029fee
- name: github.com/tendermint/go-events
version: f8ffbfb2be3483e9e7927495590a727f51c0c11f
- name: github.com/tendermint/go-flowrate
version: a20c98e61957faa93b4014fbd902f20ab9317a6a
version: 438b16f1f84ef002d7408ecd6fc3a3974cbc9559
subpackages:
- flowrate
- name: github.com/tendermint/go-logger
version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2
- name: github.com/tendermint/go-merkle
version: 714d4d04557fd068a7c2a1748241ce8428015a96
- name: github.com/tendermint/go-p2p
version: 17124989a93774833df33107fbf17157a7f8ef31
subpackages:
- upnp
- name: github.com/tendermint/go-rpc
version: 559613689d56eaa423b19a3a4158546beb4857de
subpackages:
- client
- server
- types
- cmd
- keys
- keys/cryptostore
- keys/server
- keys/server/types
- keys/storage/filestorage
- name: github.com/tendermint/go-wire
version: c1c9a57ab8038448ddea1714c0698f8051e5748c
- name: github.com/tendermint/log15
version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74
subpackages:
- term
- data
- data/base58
- name: github.com/tendermint/light-client
version: 478876ca34b360df62f941d5e20cdd608fa0a466
subpackages:
- certifiers
- certifiers/client
- certifiers/files
- commands
- commands/proofs
- commands/proxy
- commands/seeds
- commands/txs
- proofs
- name: github.com/tendermint/merkleeyes
version: 9fb76efa5aebe773a598f97e68e75fe53d520e70
version: c722818b460381bc5b82e38c73ff6e22a9df624d
subpackages:
- app
- client
- iavl
- name: github.com/tendermint/tendermint
version: 6bcd4242f1f336e2b2ef4f644fabaf56222b34d0
version: 11b5d11e9eec170e1d3dce165f0270d5c0759d69
subpackages:
- blockchain
- cmd/tendermint/commands
- config/tendermint
- cmd/tendermint/commands/flags
- config
- consensus
- mempool
- node
- p2p
- p2p/upnp
- proxy
- rpc/client
- rpc/core
- rpc/core/types
- rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state
- state/txindex
- state/txindex/kv
- state/txindex/null
- types
- version
- name: github.com/tendermint/tmlibs
version: 8af1c70a8be17543eb33e9bfbbcdd8371e3201cc
subpackages:
- autofile
- cli
- clist
- common
- db
- events
- flowrate
- log
- logger
- merkle
- name: golang.org/x/crypto
version: 96846453c37f0876340a66a47f3f75b1f3a6cd2d
version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e
subpackages:
- curve25519
- nacl/box
@ -137,7 +198,7 @@ imports:
- ripemd160
- salsa20/salsa
- name: golang.org/x/net
version: c8c74377599bd978aee1cf3b9b63a8634051cec2
version: feeb485667d1fdabe727840fe00adc22431bc86e
subpackages:
- context
- http2
@ -147,11 +208,11 @@ imports:
- lex/httplex
- trace
- name: golang.org/x/sys
version: ea9bcade75cb975a0b9738936568ab388b845617
version: 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03
subpackages:
- unix
- name: golang.org/x/text
version: 19e3104b43db45fca0303f489a9536087b184802
version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4
subpackages:
- secure/bidirule
- transform
@ -162,10 +223,11 @@ imports:
subpackages:
- googleapis/rpc/status
- name: google.golang.org/grpc
version: 6914ab1e338c92da4218a23d27fcd03d0ad78d46
version: 844f573616520565fdc6fb4db242321b5456fd6d
subpackages:
- codes
- credentials
- grpclb/grpc_lb_v1
- grpclog
- internal
- keepalive
@ -176,6 +238,10 @@ imports:
- status
- tap
- transport
- name: gopkg.in/go-playground/validator.v9
version: 6d8c18553ea1ac493d049edd6f102f52e618f085
- name: gopkg.in/yaml.v2
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
testImports:
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9

View File

@ -1,24 +1,53 @@
package: github.com/tendermint/basecoin
import:
- package: github.com/tendermint/go-common
version: develop
- package: github.com/gorilla/websocket
- package: github.com/pkg/errors
- package: github.com/spf13/cobra
- package: github.com/spf13/pflag
- package: github.com/spf13/viper
- package: github.com/tendermint/abci
subpackages:
- server
- types
- package: github.com/tendermint/go-crypto
version: develop
- package: github.com/tendermint/go-events
version: develop
- package: github.com/tendermint/go-logger
version: develop
- package: github.com/tendermint/go-data
version: develop
- package: github.com/tendermint/go-rpc
version: develop
subpackages:
- cmd
- keys
- package: github.com/tendermint/go-wire
subpackages:
- data
- package: github.com/tendermint/light-client
version: develop
subpackages:
- commands
- commands/proofs
- commands/seeds
- commands/txs
- proofs
- package: github.com/tendermint/merkleeyes
version: develop
subpackages:
- client
- iavl
- package: github.com/tendermint/tendermint
version: develop
- package: github.com/tendermint/abci
version: develop
- package: github.com/gorilla/websocket
version: v1.1.0
subpackages:
- config
- node
- proxy
- rpc/client
- rpc/core/types
- rpc/lib/client
- rpc/lib/types
- types
- package: github.com/tendermint/tmlibs
subpackages:
- cli
- common
- events
- log
- logger
testImport:
- package: github.com/stretchr/testify
subpackages:
- assert
- require

View File

@ -9,7 +9,6 @@ import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
)
@ -52,8 +51,7 @@ func TestCounterPlugin(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
// t.Logf("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes)
tx.Input.Signature = crypto.SignatureS{sig}
tx.Input.Signature = test1PrivAcc.Sign(signBytes)
// t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// Write request

View File

@ -2,15 +2,19 @@ package ibc
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/url"
"strconv"
"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"
merkle "github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/types"
tm "github.com/tendermint/tendermint/types"
)
@ -51,8 +55,108 @@ type Packet struct {
SrcChainID string
DstChainID string
Sequence uint64
Type string
Payload []byte
Type string // redundant now that Type() is a method on Payload ?
Payload Payload
}
func NewPacket(src, dst string, seq uint64, payload Payload) Packet {
return Packet{
SrcChainID: src,
DstChainID: dst,
Sequence: seq,
Type: payload.Type(),
Payload: payload,
}
}
// GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain.
// The sequence number counts how many packets have been sent.
// The next packet must include the latest sequence number.
func GetSequenceNumber(store types.KVStore, src, dst string) uint64 {
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
seqBytes := store.Get(sequenceKey)
if seqBytes == nil {
return 0
}
seq, err := strconv.ParseUint(string(seqBytes), 10, 64)
if err != nil {
cmn.PanicSanity(err.Error())
}
return seq
}
// SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain
func SetSequenceNumber(store types.KVStore, src, dst string, seq uint64) {
sequenceKey := toKey(_IBC, _EGRESS, src, dst)
store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10)))
}
// SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain
// using the correct sequence number. It also increments the sequence number by 1
func SaveNewIBCPacket(state types.KVStore, src, dst string, payload Payload) {
// fetch sequence number and increment by 1
seq := GetSequenceNumber(state, src, dst)
SetSequenceNumber(state, src, dst, seq+1)
// save ibc packet
packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
packet := NewPacket(src, dst, uint64(seq), payload)
save(state, packetKey, packet)
}
func GetIBCPacket(state types.KVStore, src, dst string, seq uint64) (Packet, error) {
packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq))
packetBytes := state.Get(packetKey)
var packet Packet
err := wire.ReadBinaryBytes(packetBytes, &packet)
return packet, err
}
//--------------------------------------------------------------------------------
const (
PayloadTypeBytes = byte(0x01)
PayloadTypeCoins = byte(0x02)
)
var _ = wire.RegisterInterface(
struct{ Payload }{},
wire.ConcreteType{DataPayload{}, PayloadTypeBytes},
wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins},
)
type Payload interface {
AssertIsPayload()
Type() string
ValidateBasic() abci.Result
}
func (DataPayload) AssertIsPayload() {}
func (CoinsPayload) AssertIsPayload() {}
type DataPayload []byte
func (p DataPayload) Type() string {
return "data"
}
func (p DataPayload) ValidateBasic() abci.Result {
return abci.OK
}
type CoinsPayload struct {
Address []byte
Coins types.Coins
}
func (p CoinsPayload) Type() string {
return "coin"
}
func (p CoinsPayload) ValidateBasic() abci.Result {
// TODO: validate
return abci.OK
}
//--------------------------------------------------------------------------------
@ -202,9 +306,8 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) {
chainGen := tx.BlockchainGenesis
// Parse genesis
var chainGenDoc = &tm.GenesisDoc{}
var err error
wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err)
chainGenDoc := new(tm.GenesisDoc)
err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc)
if err != nil {
sm.res.Code = IBCCodeEncodingError
sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error()
@ -298,8 +401,28 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) {
sm.res.Log = "Already exists"
return
}
// Execute the payload
switch payload := tx.Packet.Payload.(type) {
case DataPayload:
// do nothing
case CoinsPayload:
// ensure enough coins were sent in tx to cover the payload coins
if !sm.ctx.Coins.IsGTE(payload.Coins) {
sm.res.Code = abci.CodeType_InsufficientFunds
sm.res.Log = fmt.Sprintf("Not enough funds sent in tx (%v) to send %v via IBC", sm.ctx.Coins, payload.Coins)
return
}
// deduct coins from context
sm.ctx.Coins = sm.ctx.Coins.Minus(payload.Coins)
}
// Save new Packet
save(sm.store, packetKey, packet)
// set the sequence number
SetSequenceNumber(sm.store, packet.SrcChainID, packet.DstChainID, packet.Sequence)
}
func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
@ -326,7 +449,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
return
}
// Save new Packet
// Save new Packet (just for fun)
save(sm.store, packetKeyIngress, packet)
// Load Header and make sure it exists
@ -355,10 +478,24 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
if !ok {
sm.res.Code = IBCCodeInvalidProof
sm.res.Log = "Proof is invalid"
sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof)
return
}
// Execute payload
switch payload := packet.Payload.(type) {
case DataPayload:
// do nothing
case CoinsPayload:
// Add coins to destination account
acc := types.GetAccount(sm.store, payload.Address)
if acc == nil {
acc = &types.Account{}
}
acc.Balance = acc.Balance.Plus(payload.Coins)
types.SetAccount(sm.store, payload.Address, acc)
}
return
}

View File

@ -2,18 +2,22 @@ package ibc
import (
"bytes"
"encoding/json"
"sort"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types"
"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"
"github.com/tendermint/merkleeyes/iavl"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/basecoin/types"
tm "github.com/tendermint/tendermint/types"
)
@ -30,7 +34,7 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAcc
name := cmn.Fmt("%v_val_%v", chainID, i)
privAcc := types.PrivAccountFromSecret(name)
genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{
PubKey: privAcc.PubKey.PubKey,
PubKey: privAcc.PubKey,
Amount: 1,
Name: name,
})
@ -64,23 +68,65 @@ func (pas PrivAccountsByAddress) Swap(i, j int) {
//--------------------------------------------------------------------------------
func TestIBCPlugin(t *testing.T) {
assert := assert.New(t)
var testGenesisDoc = `{
"app_hash": "",
"chain_id": "test_chain_1",
"genesis_time": "0001-01-01T00:00:00.000Z",
"validators": [
{
"amount": 10,
"name": "",
"pub_key": {
"type": "ed25519",
"data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
}
}
],
"app_options": {
"accounts": [
{
"pub_key": {
"type": "ed25519",
"data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"
},
"coins": [
{
"denom": "mycoin",
"amount": 9007199254740992
}
]
}
]
}
}`
func TestIBCGenesisFromString(t *testing.T) {
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.NewCallContext(nil, nil, types.Coins{})
registerChain(t, ibcPlugin, store, ctx, "test_chain", testGenesisDoc)
}
//--------------------------------------------------------------------------------
func TestIBCPluginRegister(t *testing.T) {
require := require.New(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{},
}
ctx := types.NewCallContext(nil, nil, types.Coins{})
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1)
genDoc_1, _ := genGenesisDoc(chainID_1, 4)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Register a malformed chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
@ -89,20 +135,10 @@ func TestIBCPlugin(t *testing.T) {
Genesis: "<THIS IS NOT JSON>",
},
}}))
assert.Equal(IBCCodeEncodingError, res.Code)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, IBCCodeEncodingError)
// Successfully register a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.True(res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// Duplicate request fails
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
@ -111,74 +147,82 @@ func TestIBCPlugin(t *testing.T) {
Genesis: string(genDocJSON_1),
},
}}))
assert.Equal(IBCCodeChainAlreadyExists, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, IBCCodeChainAlreadyExists)
}
func TestIBCPluginPost(t *testing.T) {
require := require.New(t)
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.NewCallContext(nil, nil, types.Coins{})
chainID_1 := "test_chain"
genDoc_1, _ := genGenesisDoc(chainID_1, 4)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Register a chain
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// 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 := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_OK)
// Post a duplicate packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(IBCCodePacketAlreadyExists, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, IBCCodePacketAlreadyExists)
}
func TestIBCPluginPayloadBytes(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.NewCallContext(nil, nil, types.Coins{})
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Register a chain
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// Create a new packet (for testing)
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assertAndLog(t, store, res, abci.CodeType_OK)
// 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
}
header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
// 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
}
commit := constructCommit(privAccs_1, header)
// Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_OK)
// Get proof for the packet
packetKey := toKey(_IBC, _EGRESS,
@ -192,7 +236,7 @@ func TestIBCPlugin(t *testing.T) {
Prove: true,
})
assert.Nil(err)
var proof *merkle.IAVLProof
var proof *iavl.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(err)
@ -203,169 +247,74 @@ func TestIBCPlugin(t *testing.T) {
Packet: packet,
Proof: proof,
}}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_OK)
}
func TestIBCPluginBadCommit(t *testing.T) {
func TestIBCPluginPayloadCoins(t *testing.T) {
assert := assert.New(t)
require := require.New(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{},
coins := types.Coins{
types.Coin{
Denom: "mycoin",
Amount: 100,
},
}
ctx := types.NewCallContext(nil, nil, coins)
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Successfully register a chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
// Register a chain
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// send coins to this addr on the other chain
destinationAddr := []byte("some address")
coinsBad := types.Coins{types.Coin{"mycoin", 200}}
coinsGood := types.Coins{types.Coin{"mycoin", 1}}
// Try to send too many coins
packet := NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
Address: destinationAddr,
Coins: coinsBad,
})
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.True(res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_InsufficientFunds)
// 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(IBCCodeInvalidCommit, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
}
func TestIBCPluginBadProof(t *testing.T) {
assert := assert.New(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(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"),
}
// Send a small enough number of coins
packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
Address: destinationAddr,
Coins: coinsGood,
})
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_OK)
// 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
}
header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
// 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
}
commit := constructCommit(privAccs_1, header)
// Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
assertAndLog(t, store, res, abci.CodeType_OK)
// Get proof for the packet
packetKey := toKey(_IBC, _EGRESS,
@ -379,7 +328,120 @@ func TestIBCPluginBadProof(t *testing.T) {
Prove: true,
})
assert.Nil(err)
var proof *merkle.IAVLProof
var proof *iavl.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(err)
// Account should be empty before the tx
acc := types.GetAccount(store, destinationAddr)
assert.Nil(acc)
// Post a packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{
FromChainID: "test_chain",
FromChainHeight: 999,
Packet: packet,
Proof: proof,
}}))
assertAndLog(t, store, res, abci.CodeType_OK)
// Account should now have some coins
acc = types.GetAccount(store, destinationAddr)
assert.Equal(acc.Balance, coinsGood)
}
func TestIBCPluginBadCommit(t *testing.T) {
require := require.New(t)
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.NewCallContext(nil, nil, types.Coins{})
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Successfully register a chain
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// Construct a Header
header := newHeader("test_chain", 999, nil, []byte("must_exist"))
// Construct a Commit that signs above header
commit := constructCommit(privAccs_1, header)
// Update a chain with a broken commit
// Modify the first byte of the first signature
sig := commit.Precommits[0].Signature.Unwrap().(crypto.SignatureEd25519)
sig[0] += 1
commit.Precommits[0].Signature = sig.Wrap()
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assertAndLog(t, store, res, IBCCodeInvalidCommit)
}
func TestIBCPluginBadProof(t *testing.T) {
assert := assert.New(t)
require := require.New(t)
eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity
ibcPlugin := New()
ctx := types.NewCallContext(nil, nil, types.Coins{})
chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4)
genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Successfully register a chain
registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
// Create a new packet (for testing)
packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet,
}}))
assertAndLog(t, store, res, abci.CodeType_OK)
// Construct a Header that includes the above packet.
store.Sync()
resCommit := eyesClient.CommitSync()
appHash := resCommit.Data
header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
// Construct a Commit that signs above header
commit := constructCommit(privAccs_1, header)
// Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header,
Commit: commit,
}}))
assertAndLog(t, store, res, abci.CodeType_OK)
// 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(err)
var proof *iavl.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(err)
@ -393,7 +455,58 @@ func TestIBCPluginBadProof(t *testing.T) {
Packet: packet,
Proof: proof,
}}))
assert.Equal(IBCCodeInvalidProof, res.Code, res.Log)
assertAndLog(t, store, res, IBCCodeInvalidProof)
}
//-------------------------------------
// utils
func assertAndLog(t *testing.T, store *types.KVCache, res abci.Result, codeExpected abci.CodeType) {
assert := assert.New(t)
assert.Equal(codeExpected, res.Code, res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
}
func newHeader(chainID string, height int, appHash, valHash []byte) tm.Header {
return tm.Header{
ChainID: chainID,
Height: height,
AppHash: appHash,
ValidatorsHash: valHash,
}
}
func registerChain(t *testing.T, ibcPlugin *IBCPlugin, store *types.KVCache, ctx types.CallContext, chainID, genDoc string) {
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
BlockchainGenesis{
ChainID: chainID,
Genesis: genDoc,
},
}}))
assertAndLog(t, store, res, abci.CodeType_OK)
}
func constructCommit(privAccs []types.PrivAccount, header tm.Header) tm.Commit {
blockHash := header.Hash()
blockID := tm.BlockID{Hash: blockHash}
commit := tm.Commit{
BlockID: blockID,
Precommits: make([]*tm.Vote, len(privAccs)),
}
for i, privAcc := range privAccs {
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
}
return commit
}

View File

@ -9,9 +9,9 @@ import (
"time"
"github.com/gorilla/websocket"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-rpc/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/go-wire"
_ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types
)

View File

@ -2,14 +2,15 @@ package state
import (
abci "github.com/tendermint/abci/types"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/events"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-events"
)
// If the tx is invalid, a TMSP error will be returned.
func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) abci.Result {
chainID := state.GetChainID()
// Exec tx
@ -95,11 +96,11 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
signBytes := tx.SignBytes(chainID)
res = validateInputAdvanced(inAcc, signBytes, tx.Input)
if res.IsErr() {
log.Info(cmn.Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res))
state.logger.Info(cmn.Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res))
return res.PrependLog("in validateInputAdvanced()")
}
if !tx.Input.Coins.IsGTE(types.Coins{tx.Fee}) {
log.Info(cmn.Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
state.logger.Info(cmn.Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address))
return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("input coins is %v, but fee is %v", tx.Input.Coins, types.Coins{tx.Fee}))
}
@ -131,7 +132,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
res = plugin.RunTx(cache, ctx, tx.Data)
if res.IsOK() {
cache.CacheSync()
log.Info("Successful execution")
state.logger.Info("Successful execution")
// Fire events
/*
if evc != nil {
@ -144,7 +145,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
}
*/
} else {
log.Info("AppTx failed", "error", res)
state.logger.Info("AppTx failed", "error", res)
// Just return the coins and return.
inAccCopy.Balance = inAccCopy.Balance.Plus(coins)
// But take the gas
@ -190,17 +191,23 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
}
for _, out := range outs {
chain, outAddress, _ := out.ChainAndAddress() // already validated
if chain != nil {
// we dont need an account for the other chain.
// we'll just create an outgoing ibc packet
continue
}
// Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok {
if _, ok := accounts[string(outAddress)]; ok {
return nil, abci.ErrBaseDuplicateAddress
}
acc := state.GetAccount(out.Address)
acc := state.GetAccount(outAddress)
// output account may be nil (new)
if acc == nil {
// zero value is valid, empty account
acc = &types.Account{}
}
accounts[string(out.Address)] = acc
accounts[string(outAddress)] = acc
}
return accounts, abci.OK
}
@ -244,7 +251,7 @@ func validateInputAdvanced(acc *types.Account, signBytes []byte, in types.TxInpu
return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("balance is %v, tried to send %v", balance, in.Coins))
}
// Check signatures
if !acc.PubKey.VerifyBytes(signBytes, in.Signature.Signature) {
if !acc.PubKey.VerifyBytes(signBytes, in.Signature) {
return abci.ErrBaseInvalidSignature.AppendLog(cmn.Fmt("SignBytes: %X", signBytes))
}
return abci.OK
@ -282,15 +289,22 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun
}
}
func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
func adjustByOutputs(state *State, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) {
for _, out := range outs {
acc := accounts[string(out.Address)]
destChain, outAddress, _ := out.ChainAndAddress() // already validated
if destChain != nil {
payload := ibc.CoinsPayload{outAddress, out.Coins}
ibc.SaveNewIBCPacket(state, state.GetChainID(), string(destChain), payload)
continue
}
acc := accounts[string(outAddress)]
if acc == nil {
cmn.PanicSanity("adjustByOutputs() expects account in accounts")
}
acc.Balance = acc.Balance.Plus(out.Coins)
if !isCheckTx {
state.SetAccount(out.Address, acc)
state.SetAccount(outAddress, acc)
}
}
}

View File

@ -6,6 +6,9 @@ import (
"github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types"
)
@ -32,11 +35,6 @@ func (et *execTest) signTx(tx *types.SendTx, accsIn ...types.PrivAccount) {
types.SignTx(et.chainID, tx, accsIn...)
}
// make tx from accsIn to et.accOut
func (et *execTest) getTx(seq int, accOut types.PrivAccount, accsIn ...types.PrivAccount) *types.SendTx {
return types.GetTx(seq, accOut, accsIn...)
}
// returns the final balance and expected balance for input and output accounts
func (et *execTest) exec(tx *types.SendTx, checkTx bool) (res abci.Result, inGot, inExp, outGot, outExp types.Coins) {
initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
@ -63,6 +61,7 @@ func (et *execTest) reset() {
et.store = types.NewMemKVStore()
et.state = NewState(et.store)
et.state.SetLogger(log.TestingLogger())
et.state.SetChainID(et.chainID)
// NOTE we dont run acc2State here
@ -83,19 +82,19 @@ func TestGetInputs(t *testing.T) {
//test getInputs for registered, non-registered account
et.reset()
txs := types.Accs2TxInputs(1, et.accIn)
acc, res = getInputs(et.state, txs)
inputs := types.Accs2TxInputs(1, et.accIn)
acc, res = getInputs(et.state, inputs)
assert.True(res.IsErr(), "getInputs: expected error when using getInput with non-registered Input")
et.acc2State(et.accIn)
acc, res = getInputs(et.state, txs)
acc, res = getInputs(et.state, inputs)
assert.True(res.IsOK(), "getInputs: expected to getInput from registered Input")
//test sending duplicate accounts
et.reset()
et.acc2State(et.accIn, et.accIn, et.accIn)
txs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn)
acc, res = getInputs(et.state, txs)
inputs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn)
acc, res = getInputs(et.state, inputs)
assert.True(res.IsErr(), "getInputs: expected error when sending duplicate accounts")
}
@ -110,24 +109,24 @@ func TestGetOrMakeOutputs(t *testing.T) {
//test sending duplicate accounts
et.reset()
txs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn)
_, res = getOrMakeOutputs(et.state, nil, txs)
outputs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn)
_, res = getOrMakeOutputs(et.state, nil, outputs)
assert.True(res.IsErr(), "getOrMakeOutputs: expected error when sending duplicate accounts")
//test sending to existing/new account
et.reset()
txs1 := types.Accs2TxOutputs(et.accIn)
txs2 := types.Accs2TxOutputs(et.accOut)
outputs1 := types.Accs2TxOutputs(et.accIn)
outputs2 := types.Accs2TxOutputs(et.accOut)
et.acc2State(et.accIn)
_, res = getOrMakeOutputs(et.state, nil, txs1)
_, res = getOrMakeOutputs(et.state, nil, outputs1)
assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to existing account")
mapRes2, res := getOrMakeOutputs(et.state, nil, txs2)
mapRes2, res := getOrMakeOutputs(et.state, nil, outputs2)
assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to new account")
//test the map results
_, map2ok := mapRes2[string(txs2[0].Address)]
_, map2ok := mapRes2[string(outputs2[0].Address)]
assert.True(map2ok, "getOrMakeOutputs: account output does not contain new account map item")
}
@ -137,12 +136,12 @@ func TestValidateInputsBasic(t *testing.T) {
et := newExecTest()
//validate input basic
txs := types.Accs2TxInputs(1, et.accIn)
res := validateInputsBasic(txs)
inputs := types.Accs2TxInputs(1, et.accIn)
res := validateInputsBasic(inputs)
assert.True(res.IsOK(), "validateInputsBasic: expected no error on good tx input. Error: %v", res.Error())
txs[0].Coins[0].Amount = 0
res = validateInputsBasic(txs)
inputs[0].Coins[0].Amount = 0
res = validateInputsBasic(inputs)
assert.True(res.IsErr(), "validateInputsBasic: expected error on bad tx input")
}
@ -157,28 +156,28 @@ func TestValidateInputsAdvanced(t *testing.T) {
accIn3 := types.MakeAcc("fooz")
//validate inputs advanced
txs := et.getTx(1, et.accOut, accIn1, accIn2, accIn3)
tx := types.MakeSendTx(1, et.accOut, accIn1, accIn2, accIn3)
et.acc2State(accIn1, accIn2, accIn3, et.accOut)
accMap, res := getInputs(et.state, txs.Inputs)
accMap, res := getInputs(et.state, tx.Inputs)
assert.True(res.IsOK(), "validateInputsAdvanced: error retrieving accMap. Error: %v", res.Error())
signBytes := txs.SignBytes(et.chainID)
signBytes := tx.SignBytes(et.chainID)
//test bad case, unsigned
totalCoins, res := validateInputsAdvanced(accMap, signBytes, txs.Inputs)
totalCoins, res := validateInputsAdvanced(accMap, signBytes, tx.Inputs)
assert.True(res.IsErr(), "validateInputsAdvanced: expected an error on an unsigned tx input")
//test good case sgined
et.signTx(txs, accIn1, accIn2, accIn3, et.accOut)
totalCoins, res = validateInputsAdvanced(accMap, signBytes, txs.Inputs)
et.signTx(tx, accIn1, accIn2, accIn3, et.accOut)
totalCoins, res = validateInputsAdvanced(accMap, signBytes, tx.Inputs)
assert.True(res.IsOK(), "validateInputsAdvanced: expected no error on good tx input. Error: %v", res.Error())
txsTotalCoins := txs.Inputs[0].Coins.
Plus(txs.Inputs[1].Coins).
Plus(txs.Inputs[2].Coins)
txTotalCoins := tx.Inputs[0].Coins.
Plus(tx.Inputs[1].Coins).
Plus(tx.Inputs[2].Coins)
assert.True(totalCoins.IsEqual(txsTotalCoins),
"ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txsTotalCoins, totalCoins)
assert.True(totalCoins.IsEqual(txTotalCoins),
"ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txTotalCoins, totalCoins)
}
func TestValidateInputAdvanced(t *testing.T) {
@ -186,31 +185,31 @@ func TestValidateInputAdvanced(t *testing.T) {
et := newExecTest()
//validate input advanced
txs := et.getTx(1, et.accOut, et.accIn)
tx := types.MakeSendTx(1, et.accOut, et.accIn)
et.acc2State(et.accIn, et.accOut)
signBytes := txs.SignBytes(et.chainID)
signBytes := tx.SignBytes(et.chainID)
//unsigned case
res := validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
res := validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
assert.True(res.IsErr(), "validateInputAdvanced: expected error on tx input without signature")
//good signed case
et.signTx(txs, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
assert.True(res.IsOK(), "validateInputAdvanced: expected no error on good tx input. Error: %v", res.Error())
//bad sequence case
et.accIn.Sequence = 1
et.signTx(txs, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
assert.Equal(abci.CodeType_BaseInvalidSequence, res.Code, "validateInputAdvanced: expected error on tx input with bad sequence")
et.accIn.Sequence = 0 //restore sequence
//bad balance case
et.accIn.Balance = types.Coins{{"mycoin", 2}}
et.signTx(txs, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0])
et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
assert.Equal(abci.CodeType_BaseInsufficientFunds, res.Code,
"validateInputAdvanced: expected error on tx input with insufficient funds %v", et.accIn.Sequence)
}
@ -220,12 +219,12 @@ func TestValidateOutputsAdvanced(t *testing.T) {
et := newExecTest()
//validateOutputsBasic
txs := types.Accs2TxOutputs(et.accIn)
res := validateOutputsBasic(txs)
tx := types.Accs2TxOutputs(et.accIn)
res := validateOutputsBasic(tx)
assert.True(res.IsOK(), "validateOutputsBasic: expected no error on good tx output. Error: %v", res.Error())
txs[0].Coins[0].Amount = 0
res = validateOutputsBasic(txs)
tx[0].Coins[0].Amount = 0
res = validateOutputsBasic(tx)
assert.True(res.IsErr(), "validateInputBasic: expected error on bad tx output. Error: %v", res.Error())
}
@ -234,9 +233,9 @@ func TestSumOutput(t *testing.T) {
et := newExecTest()
//SumOutput
txs := types.Accs2TxOutputs(et.accIn, et.accOut)
total := sumOutputs(txs)
assert.True(total.IsEqual(txs[0].Coins.Plus(txs[1].Coins)), "sumOutputs: total coins are not equal")
tx := types.Accs2TxOutputs(et.accIn, et.accOut)
total := sumOutputs(tx)
assert.True(total.IsEqual(tx[0].Coins.Plus(tx[1].Coins)), "sumOutputs: total coins are not equal")
}
func TestAdjustBy(t *testing.T) {
@ -269,23 +268,23 @@ func TestAdjustBy(t *testing.T) {
}
func TestExecTx(t *testing.T) {
func TestSendTx(t *testing.T) {
assert := assert.New(t)
et := newExecTest()
//ExecTx
txs := et.getTx(1, et.accOut, et.accIn)
tx := types.MakeSendTx(1, et.accOut, et.accIn)
et.acc2State(et.accIn)
et.acc2State(et.accOut)
et.signTx(txs, et.accIn)
et.signTx(tx, et.accIn)
//Bad Balance
et.accIn.Balance = types.Coins{{"mycoin", 2}}
et.acc2State(et.accIn)
res, _, _, _, _ := et.exec(txs, true)
res, _, _, _, _ := et.exec(tx, true)
assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res)
res, balIn, balInExp, balOut, balOutExp := et.exec(txs, false)
res, balIn, balInExp, balOut, balOutExp := et.exec(tx, false)
assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
assert.False(balIn.IsEqual(balInExp),
"ExecTx/Bad DeliverTx: balance shouldn't be equal for accIn: got %v, expected: %v", balIn, balInExp)
@ -296,17 +295,59 @@ func TestExecTx(t *testing.T) {
et.reset()
et.acc2State(et.accIn)
et.acc2State(et.accOut)
res, _, _, _, _ = et.exec(txs, true)
res, _, _, _, _ = et.exec(tx, true)
assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
//Regular DeliverTx
et.reset()
et.acc2State(et.accIn)
et.acc2State(et.accOut)
res, balIn, balInExp, balOut, balOutExp = et.exec(txs, false)
res, balIn, balInExp, balOut, balOutExp = et.exec(tx, false)
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
assert.True(balIn.IsEqual(balInExp),
"ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp)
assert.True(balOut.IsEqual(balOutExp),
"ExecTx/good DeliverTx: unexpected change in output balance, got: %v, expected: %v", balOut, balOutExp)
}
func TestSendTxIBC(t *testing.T) {
assert := assert.New(t)
et := newExecTest()
//ExecTx
chainID2 := "otherchain"
tx := types.MakeSendTx(1, et.accOut, et.accIn)
dstAddress := tx.Outputs[0].Address
tx.Outputs[0].Address = []byte(chainID2 + "/" + string(tx.Outputs[0].Address))
et.acc2State(et.accIn)
et.signTx(tx, et.accIn)
//Regular DeliverTx
et.reset()
et.acc2State(et.accIn)
initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
res := ExecTx(et.state, nil, tx, false, nil)
balIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
decrBalInExp := tx.Outputs[0].Coins.Plus(types.Coins{tx.Fee}) //expected decrease in balance In
balInExp := initBalIn.Minus(decrBalInExp)
assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
assert.True(balIn.IsEqual(balInExp),
"ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp)
packet, err := ibc.GetIBCPacket(et.state, et.chainID, chainID2, 0)
assert.Nil(err)
assert.Equal(packet.SrcChainID, et.chainID)
assert.Equal(packet.DstChainID, chainID2)
assert.Equal(packet.Sequence, uint64(0))
assert.Equal(packet.Type, "coin")
coins, ok := packet.Payload.(ibc.CoinsPayload)
assert.True(ok)
assert.Equal(coins.Coins, tx.Outputs[0].Coins)
assert.EqualValues(coins.Address, dstAddress)
}

View File

@ -1,7 +0,0 @@
package state
import (
"github.com/tendermint/go-logger"
)
var log = logger.New("module", "state")

View File

@ -3,9 +3,8 @@ package state
import (
abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
)
// CONTRACT: State should be quick to copy.
@ -15,6 +14,7 @@ type State struct {
store types.KVStore
readCache map[string][]byte // optional, for caching writes to store
writeCache *types.KVCache // optional, for caching writes w/o writing to store
logger log.Logger
}
func NewState(store types.KVStore) *State {
@ -23,9 +23,14 @@ func NewState(store types.KVStore) *State {
store: store,
readCache: make(map[string][]byte),
writeCache: nil,
logger: log.NewNopLogger(),
}
}
func (s *State) SetLogger(l log.Logger) {
s.logger = l
}
func (s *State) SetChainID(chainID string) {
s.chainID = chainID
s.store.Set([]byte("base/chain_id"), []byte(chainID))
@ -57,11 +62,11 @@ func (s *State) Set(key []byte, value []byte) {
}
func (s *State) GetAccount(addr []byte) *types.Account {
return GetAccount(s, addr)
return types.GetAccount(s, addr)
}
func (s *State) SetAccount(addr []byte, acc *types.Account) {
SetAccount(s, addr, acc)
types.SetAccount(s, addr, acc)
}
func (s *State) CacheWrap() *State {
@ -71,6 +76,7 @@ func (s *State) CacheWrap() *State {
store: cache,
readCache: nil,
writeCache: cache,
logger: s.logger,
}
}
@ -89,28 +95,3 @@ func (s *State) Commit() abci.Result {
}
}
//----------------------------------------
func AccountKey(addr []byte) []byte {
return append([]byte("base/a/"), addr...)
}
func GetAccount(store types.KVStore, addr []byte) *types.Account {
data := store.Get(AccountKey(addr))
if len(data) == 0 {
return nil
}
var acc *types.Account
err := wire.ReadBinaryBytes(data, &acc)
if err != nil {
panic(Fmt("Error reading account %X error: %v",
data, err.Error()))
}
return acc
}
func SetAccount(store types.KVStore, addr []byte, acc *types.Account) {
accBytes := wire.BinaryBytes(acc)
store.Set(AccountKey(addr), accBytes)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/tendermint/basecoin/types"
eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
"github.com/stretchr/testify/assert"
)
@ -16,6 +17,7 @@ func TestState(t *testing.T) {
//States and Stores for tests
store := types.NewMemKVStore()
state := NewState(store)
state.SetLogger(log.TestingLogger())
cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0)
@ -29,12 +31,14 @@ func TestState(t *testing.T) {
reset := func() {
store = types.NewMemKVStore()
state = NewState(store)
state.SetLogger(log.TestingLogger())
cache = state.CacheWrap()
}
//set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() {
state = NewState(eyesCli)
state.SetLogger(log.TestingLogger())
cache = state.CacheWrap()
}

View File

@ -6,12 +6,11 @@ import (
"github.com/gorilla/websocket"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
rpcclient "github.com/tendermint/go-rpc/client"
"github.com/tendermint/go-rpc/types"
wire "github.com/tendermint/go-wire"
_ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types
"github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/tendermint/rpc/lib/types"
cmn "github.com/tendermint/tmlibs/common"
)
func main() {
@ -67,16 +66,18 @@ func main() {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := root.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
tx.Inputs[0].Signature = sig
//fmt.Println("tx:", tx)
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes})
//request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes})
request, err := rpctypes.MapToRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes})
if err != nil {
cmn.Exit("cannot encode request: " + err.Error())
}
reqBytes := wire.JSONBytes(request)
//fmt.Print(".")
err := ws.WriteMessage(websocket.TextMessage, reqBytes)
err = ws.WriteMessage(websocket.TextMessage, reqBytes)
if err != nil {
cmn.Exit("writing websocket request: " + err.Error())
}
@ -118,15 +119,18 @@ func main() {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := privAccountA.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
tx.Inputs[0].Signature = sig
//fmt.Println("tx:", tx)
// Write request
txBytes := wire.BinaryBytes(struct{ types.Tx }{tx})
request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes})
request, err := rpctypes.MapToRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes})
if err != nil {
cmn.Exit("cannot encode request: " + err.Error())
}
reqBytes := wire.JSONBytes(request)
//fmt.Print(".")
err := ws.WriteMessage(websocket.TextMessage, reqBytes)
err = ws.WriteMessage(websocket.TextMessage, reqBytes)
if err != nil {
cmn.Exit("writing websocket request: " + err.Error())
}

View File

@ -8,16 +8,17 @@ import (
"github.com/stretchr/testify/require"
"github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
wire "github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/tmlibs/log"
)
func TestSendTx(t *testing.T) {
eyesCli := eyescli.NewLocalClient("", 0)
chainID := "test_chain_id"
bcApp := app.NewBasecoin(eyesCli)
bcApp.SetLogger(log.TestingLogger().With("module", "app"))
bcApp.SetOption("base/chain_id", chainID)
// t.Log(bcApp.Info())
@ -50,7 +51,7 @@ func TestSendTx(t *testing.T) {
signBytes := tx.SignBytes(chainID)
// t.Log("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
tx.Inputs[0].Signature = sig
// t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(types.TxS{tx}))
// Write request
@ -102,7 +103,7 @@ func TestSequence(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := test1PrivAcc.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
tx.Inputs[0].Signature = sig
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// Write request
@ -146,7 +147,7 @@ func TestSequence(t *testing.T) {
// Sign request
signBytes := tx.SignBytes(chainID)
sig := privAccountA.Sign(signBytes)
tx.Inputs[0].Signature = crypto.SignatureS{sig}
tx.Inputs[0].Signature = sig
// t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address)
// Write request

View File

@ -4,12 +4,13 @@ import (
"fmt"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
)
type Account struct {
PubKey crypto.PubKeyS `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance Coins `json:"coins"`
PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"`
Balance Coins `json:"coins"`
}
func (acc *Account) Copy() *Account {
@ -31,7 +32,7 @@ func (acc *Account) String() string {
//----------------------------------------
type PrivAccount struct {
crypto.PrivKeyS
crypto.PrivKey
Account
}
@ -49,3 +50,26 @@ type AccountGetterSetter interface {
GetAccount(addr []byte) *Account
SetAccount(addr []byte, acc *Account)
}
func AccountKey(addr []byte) []byte {
return append([]byte("base/a/"), addr...)
}
func GetAccount(store KVStore, addr []byte) *Account {
data := store.Get(AccountKey(addr))
if len(data) == 0 {
return nil
}
var acc *Account
err := wire.ReadBinaryBytes(data, &acc)
if err != nil {
panic(fmt.Sprintf("Error reading account %X error: %v",
data, err.Error()))
}
return acc
}
func SetAccount(store KVStore, addr []byte, acc *Account) {
accBytes := wire.BinaryBytes(acc)
store.Set(AccountKey(addr), accBytes)
}

View File

@ -3,8 +3,11 @@ package types
import (
"fmt"
"regexp"
"sort"
"strconv"
"strings"
"github.com/pkg/errors"
)
type Coin struct {
@ -17,22 +20,26 @@ func (coin Coin) String() string {
}
//regex codes for extracting coins from string
var reDenom = regexp.MustCompile("([^\\d\\W]+)")
var reDenom = regexp.MustCompile("")
var reAmt = regexp.MustCompile("(\\d+)")
func ParseCoin(str string) (Coin, error) {
var reCoin = regexp.MustCompile("^([[:digit:]]+)[[:space:]]*([[:alpha:]]+)$")
func ParseCoin(str string) (Coin, error) {
var coin Coin
if len(str) > 0 {
amt, err := strconv.Atoi(reAmt.FindString(str))
if err != nil {
return coin, err
}
denom := reDenom.FindString(str)
coin = Coin{denom, int64(amt)}
matches := reCoin.FindStringSubmatch(strings.TrimSpace(str))
if matches == nil {
return coin, errors.Errorf("%s is invalid coin definition", str)
}
// parse the amount (should always parse properly)
amt, err := strconv.Atoi(matches[1])
if err != nil {
return coin, err
}
coin = Coin{matches[2], int64(amt)}
return coin, nil
}
@ -53,18 +60,26 @@ func (coins Coins) String() string {
}
func ParseCoins(str string) (Coins, error) {
// empty string is empty list...
if len(str) == 0 {
return nil, nil
}
split := strings.Split(str, ",")
var coins []Coin
var coins Coins
for _, el := range split {
if len(el) > 0 {
coin, err := ParseCoin(el)
if err != nil {
return coins, err
}
coins = append(coins, coin)
coin, err := ParseCoin(el)
if err != nil {
return coins, err
}
coins = append(coins, coin)
}
// ensure they are in proper order, to avoid random failures later
coins.Sort()
if !coins.IsValid() {
return nil, errors.Errorf("ParseCoins invalid: %#v", coins)
}
return coins, nil
@ -195,3 +210,10 @@ func (coins Coins) IsNonnegative() bool {
}
return true
}
/*** Implement Sort interface ***/
func (c Coins) Len() int { return len(c) }
func (c Coins) Less(i, j int) bool { return c[i].Denom < c[j].Denom }
func (c Coins) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
func (c Coins) Sort() { sort.Sort(c) }

View File

@ -4,7 +4,6 @@ import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestCoins(t *testing.T) {
@ -56,30 +55,85 @@ func TestCoins(t *testing.T) {
//Test the parse coin and parse coins functionality
func TestParse(t *testing.T) {
assert, require := assert.New(t), require.New(t)
assert := assert.New(t)
makeCoin := func(str string) Coin {
coin, err := ParseCoin(str)
require.Nil(err)
return coin
cases := []struct {
input string
valid bool // if false, we expect an error on parse
expected Coins // if valid is true, make sure this is returned
}{
{"", true, nil},
{"1foo", true, Coins{{"foo", 1}}},
{"10bar", true, Coins{{"bar", 10}}},
{"99bar,1foo", true, Coins{{"bar", 99}, {"foo", 1}}},
{"98 bar , 1 foo ", true, Coins{{"bar", 98}, {"foo", 1}}},
{" 55\t \t bling\n", true, Coins{{"bling", 55}}},
{"2foo, 97 bar", true, Coins{{"bar", 97}, {"foo", 2}}},
{"5 mycoin,", false, nil}, // no empty coins in a list
{"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name
{"11me coin, 12you coin", false, nil}, // no spaces in coin names
{"1.2btc", false, nil}, // amount must be integer
{"5foo-bar", false, nil}, // once more, only letters in coin name
}
makeCoins := func(str string) Coins {
coin, err := ParseCoins(str)
require.Nil(err)
return coin
for _, tc := range cases {
res, err := ParseCoins(tc.input)
if !tc.valid {
assert.NotNil(err, "%s: %#v", tc.input, res)
} else if assert.Nil(err, "%s: %+v", tc.input, err) {
assert.Equal(tc.expected, res)
}
}
//testing ParseCoin Function
assert.Equal(Coin{}, makeCoin(""), "ParseCoin makes bad empty coin")
assert.Equal(Coin{"fooCoin", 1}, makeCoin("1fooCoin"), "ParseCoin makes bad coins")
assert.Equal(Coin{"barCoin", 10}, makeCoin("10 barCoin"), "ParseCoin makes bad coins")
//testing ParseCoins Function
assert.True(Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")),
"ParseCoins doesn't parse a single coin")
assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")),
"ParseCoins doesn't properly parse two coins")
assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")),
"ParseCoins doesn't properly parse two coins which use spaces")
}
func TestSortCoins(t *testing.T) {
assert := assert.New(t)
good := Coins{
Coin{"GAS", 1},
Coin{"MINERAL", 1},
Coin{"TREE", 1},
}
empty := Coins{
Coin{"GOLD", 0},
}
badSort1 := Coins{
Coin{"TREE", 1},
Coin{"GAS", 1},
Coin{"MINERAL", 1},
}
badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order
Coin{"GAS", 1},
Coin{"TREE", 1},
Coin{"MINERAL", 1},
}
badAmt := Coins{
Coin{"GAS", 1},
Coin{"TREE", 0},
Coin{"MINERAL", 1},
}
dup := Coins{
Coin{"GAS", 1},
Coin{"GAS", 1},
Coin{"MINERAL", 1},
}
cases := []struct {
coins Coins
before, after bool // valid before/after sort
}{
{good, true, true},
{empty, false, false},
{badSort1, false, true},
{badSort2, false, true},
{badAmt, false, false},
{dup, false, false},
}
for _, tc := range cases {
assert.Equal(tc.before, tc.coins.IsValid())
tc.coins.Sort()
assert.Equal(tc.after, tc.coins.IsValid())
}
}

View File

@ -4,7 +4,7 @@ import (
"container/list"
"fmt"
. "github.com/tendermint/go-common"
. "github.com/tendermint/tmlibs/common"
)
type KVStore interface {

View File

@ -3,18 +3,19 @@ package types
// Helper functions for testing
import (
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
)
// Creates a PrivAccount from secret.
// The amount is not set.
func PrivAccountFromSecret(secret string) PrivAccount {
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret))
privKey :=
crypto.GenPrivKeyEd25519FromSecret([]byte(secret)).Wrap()
privAccount := PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey},
PrivKey: privKey,
Account: Account{
PubKey: crypto.PubKeyS{privKey.PubKey()},
PubKey: privKey.PubKey(),
},
}
return privAccount
@ -30,10 +31,10 @@ func RandAccounts(num int, minAmount int64, maxAmount int64) []PrivAccount {
balance += cmn.RandInt64() % (maxAmount - minAmount)
}
privKey := crypto.GenPrivKeyEd25519()
pubKey := crypto.PubKeyS{privKey.PubKey()}
privKey := crypto.GenPrivKeyEd25519().Wrap()
pubKey := privKey.PubKey()
privAccs[i] = PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey},
PrivKey: privKey,
Account: Account{
PubKey: pubKey,
Balance: Coins{Coin{"", balance}},
@ -85,20 +86,20 @@ func Accs2TxOutputs(accs ...PrivAccount) []TxOutput {
return txs
}
func GetTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx {
txs := &SendTx{
func MakeSendTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx {
tx := &SendTx{
Gas: 0,
Fee: Coin{"mycoin", 1},
Inputs: Accs2TxInputs(seq, accsIn...),
Outputs: Accs2TxOutputs(accOut),
}
return txs
return tx
}
func SignTx(chainID string, tx *SendTx, accs ...PrivAccount) {
signBytes := tx.SignBytes(chainID)
for i, _ := range tx.Inputs {
tx.Inputs[i].Signature = crypto.SignatureS{accs[i].Sign(signBytes)}
tx.Inputs[i].Signature = accs[i].Sign(signBytes)
}
}

View File

@ -5,10 +5,10 @@ import (
"encoding/json"
abci "github.com/tendermint/abci/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto"
"github.com/tendermint/go-data"
"github.com/tendermint/go-wire"
"github.com/tendermint/go-wire/data"
. "github.com/tendermint/tmlibs/common"
)
/*
@ -37,7 +37,7 @@ func (_ *AppTx) AssertIsTx() {}
var txMapper data.Mapper
// register both private key types with go-data (and thus go-wire)
// register both private key types with go-wire/data (and thus go-wire)
func init() {
txMapper = data.NewMapper(TxS{}).
RegisterImplementation(&SendTx{}, TxNameSend, TxTypeSend).
@ -46,7 +46,7 @@ func init() {
// TxS add json serialization to Tx
type TxS struct {
Tx
Tx `json:"unwrap"`
}
func (p TxS) MarshalJSON() ([]byte, error) {
@ -64,11 +64,11 @@ func (p *TxS) UnmarshalJSON(data []byte) (err error) {
//-----------------------------------------------------------------------------
type TxInput struct {
Address data.Bytes `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.SignatureS `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKeyS `json:"pub_key"` // Is present iff Sequence == 0
Address data.Bytes `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
}
func (txIn TxInput) ValidateBasic() abci.Result {
@ -104,13 +104,7 @@ func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput {
Sequence: sequence,
}
if sequence == 1 {
// safely wrap if needed
// TODO: extract this as utility function?
ps, ok := pubKey.(crypto.PubKeyS)
if !ok {
ps = crypto.PubKeyS{pubKey}
}
input.PubKey = ps
input.PubKey = pubKey
}
return input
}
@ -122,10 +116,33 @@ type TxOutput struct {
Coins Coins `json:"coins"` //
}
func (txOut TxOutput) ValidateBasic() abci.Result {
if len(txOut.Address) != 20 {
return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
// An output destined for another chain may be formatted as `chainID/address`.
// ChainAndAddress returns the chainID prefix and the address.
// If there is no chainID prefix, the first returned value is nil.
func (txOut TxOutput) ChainAndAddress() ([]byte, []byte, abci.Result) {
var chainPrefix []byte
address := txOut.Address
if len(address) > 20 {
spl := bytes.Split(address, []byte("/"))
if len(spl) < 2 {
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address format")
}
chainPrefix = spl[0]
address = bytes.Join(spl[1:], nil)
}
if len(address) != 20 {
return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address length")
}
return chainPrefix, address, abci.OK
}
func (txOut TxOutput) ValidateBasic() abci.Result {
_, _, r := txOut.ChainAndAddress()
if r.IsErr() {
return r
}
if !txOut.Coins.IsValid() {
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
}
@ -151,25 +168,21 @@ type SendTx struct {
func (tx *SendTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID)
sigz := make([]crypto.Signature, len(tx.Inputs))
for i, input := range tx.Inputs {
sigz[i] = input.Signature.Signature
tx.Inputs[i].Signature.Signature = nil
for i := range tx.Inputs {
sigz[i] = tx.Inputs[i].Signature
tx.Inputs[i].Signature = crypto.Signature{}
}
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
for i := range tx.Inputs {
tx.Inputs[i].Signature.Signature = sigz[i]
tx.Inputs[i].Signature = sigz[i]
}
return signBytes
}
func (tx *SendTx) SetSignature(addr []byte, sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
for i, input := range tx.Inputs {
if bytes.Equal(input.Address, addr) {
tx.Inputs[i].Signature = sigs
tx.Inputs[i].Signature = sig
return true
}
}
@ -193,18 +206,14 @@ type AppTx struct {
func (tx *AppTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID)
sig := tx.Input.Signature
tx.Input.Signature.Signature = nil
tx.Input.Signature = crypto.Signature{}
signBytes = append(signBytes, wire.BinaryBytes(tx)...)
tx.Input.Signature = sig
return signBytes
}
func (tx *AppTx) SetSignature(sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS)
if !ok {
sigs = crypto.SignatureS{sig}
}
tx.Input.Signature = sigs
tx.Input.Signature = sig
return true
}

View File

@ -6,8 +6,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
crypto "github.com/tendermint/go-crypto"
data "github.com/tendermint/go-data"
data "github.com/tendermint/go-wire/data"
)
var chainID string = "test_chain"
@ -109,7 +108,7 @@ func TestSendTxJSON(t *testing.T) {
sig := test1PrivAcc.Sign(signBytes)
// we handle both raw sig and wrapped sig the same
tx.SetSignature(test1PrivAcc.PubKey.Address(), sig)
tx2.SetSignature(test1PrivAcc.PubKey.Address(), crypto.SignatureS{sig})
tx2.SetSignature(test1PrivAcc.PubKey.Address(), sig)
assert.Equal(tx, tx2)
// let's marshal / unmarshal this with signature

View File

@ -1,7 +1,7 @@
package version
const Maj = "0"
const Min = "4"
const Fix = "1"
const Min = "5"
const Fix = "0"
const Version = "0.4.1"
const Version = "0.5.0"