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 # 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) ## 0.4.1 (April 26, 2017)
BUG FIXES: BUG FIXES:

View File

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

View File

@ -1,8 +1,8 @@
package app package app
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"fmt"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -10,8 +10,9 @@ import (
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
"github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client" 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 // make a tx sending 5mycoin from each accIn to accOut
func (at *appTest) getTx(seq int) *types.SendTx { 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) types.SignTx(at.chainID, tx, at.accIn)
return tx return tx
} }
@ -56,6 +57,7 @@ func (at *appTest) reset() {
eyesCli := eyes.NewLocalClient("", 0) eyesCli := eyes.NewLocalClient("", 0)
at.app = NewBasecoin(eyesCli) at.app = NewBasecoin(eyesCli)
at.app.SetLogger(log.TestingLogger().With("module", "app"))
res := at.app.SetOption("base/chain_id", at.chainID) res := at.app.SetOption("base/chain_id", at.chainID)
require.EqualValues(at.t, res, "Success") require.EqualValues(at.t, res, "Success")
@ -101,9 +103,11 @@ func TestSplitKey(t *testing.T) {
func TestSetOption(t *testing.T) { func TestSetOption(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t)
eyesCli := eyes.NewLocalClient("", 0) eyesCli := eyes.NewLocalClient("", 0)
app := NewBasecoin(eyesCli) app := NewBasecoin(eyesCli)
app.SetLogger(log.TestingLogger().With("module", "app"))
//testing ChainID //testing ChainID
chainID := "testChain" chainID := "testChain"
@ -111,11 +115,43 @@ func TestSetOption(t *testing.T) {
assert.EqualValues(app.GetState().GetChainID(), chainID) assert.EqualValues(app.GetState().GetChainID(), chainID)
assert.EqualValues(res, "Success") assert.EqualValues(res, "Success")
// make a nice account...
accIn := types.MakeAcc("input0") accIn := types.MakeAcc("input0")
accsInBytes, err := json.Marshal(accIn.Account) accsInBytes, err := json.Marshal(accIn.Account)
assert.Nil(err) assert.Nil(err)
res = app.SetOption("base/account", string(accsInBytes)) 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", "") res = app.SetOption("base/dslfkgjdas", "")
assert.NotEqual(res, "Success") assert.NotEqual(res, "Success")
@ -125,6 +161,7 @@ func TestSetOption(t *testing.T) {
res = app.SetOption("dslfkgjdas/szfdjzs", "") res = app.SetOption("dslfkgjdas/szfdjzs", "")
assert.NotEqual(res, "Success") assert.NotEqual(res, "Success")
} }
// Test CheckTx and DeliverTx with insufficient and sufficient balance // Test CheckTx and DeliverTx with insufficient and sufficient balance
@ -176,7 +213,5 @@ func TestQuery(t *testing.T) {
Path: "/account", Path: "/account",
Data: at.accIn.Account.PubKey.Address(), Data: at.accIn.Account.PubKey.Address(),
}) })
fmt.Println(resQueryPreCommit)
fmt.Println(resQueryPostCommit)
assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit") assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit")
} }

View File

@ -1,12 +1,15 @@
package app package app
import ( import (
"bytes"
"encoding/json" "encoding/json"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common" crypto "github.com/tendermint/go-crypto"
//tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/go-wire/data"
cmn "github.com/tendermint/tmlibs/common"
) )
func (app *Basecoin) LoadGenesis(path string) error { 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)) r := app.SetOption("base/account", string(accBytes))
// TODO: SetOption returns an error // 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 // set plugin options
for _, kv := range genDoc.AppOptions.pluginOptions { for _, kv := range genDoc.AppOptions.pluginOptions {
r := app.SetOption(kv.Key, kv.Value) 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 return nil
} }
@ -49,7 +52,7 @@ type FullGenesisDoc struct {
} }
type GenesisDoc struct { type GenesisDoc struct {
Accounts []types.Account `json:"accounts"` Accounts []GenesisAccount `json:"accounts"`
PluginOptions []json.RawMessage `json:"plugin_options"` PluginOptions []json.RawMessage `json:"plugin_options"`
pluginOptions []keyValue // unmarshaled rawmessages pluginOptions []keyValue // unmarshaled rawmessages
@ -61,11 +64,7 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) {
return nil, errors.Wrap(err, "loading genesis file") return nil, errors.Wrap(err, "loading genesis file")
} }
// the tendermint genesis is go-wire // the basecoin genesis go-wire/data :)
// tmGenesis := new(tmtypes.GenesisDoc)
// err = wire.ReadJSONBytes(bytes, tmGenesis)
// the basecoin genesis go-data :)
genDoc := new(FullGenesisDoc) genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc) err = json.Unmarshal(bytes, genDoc)
if err != nil { if err != nil {
@ -102,3 +101,40 @@ func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) {
} }
return kvz, nil 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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
cmn "github.com/tendermint/go-common" "github.com/tendermint/basecoin/types"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
eyescli "github.com/tendermint/merkleeyes/client" eyescli "github.com/tendermint/merkleeyes/client"
cmn "github.com/tendermint/tmlibs/common"
) )
const genesisFilepath = "./testdata/genesis.json" const genesisFilepath = "./testdata/genesis.json"
const genesisAcctFilepath = "./testdata/genesis2.json"
func TestLoadGenesis(t *testing.T) { func TestLoadGenesis(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
@ -34,11 +36,15 @@ func TestLoadGenesis(t *testing.T) {
// make sure balance is proper // make sure balance is proper
assert.Equal(2, len(acct.Balance)) assert.Equal(2, len(acct.Balance))
assert.EqualValues(12345, acct.Balance[0].Amount) assert.True(acct.Balance.IsValid())
assert.EqualValues("blank", acct.Balance[0].Denom) // 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 // and public key is parsed properly
apk := acct.PubKey.PubKey apk := acct.PubKey.Unwrap()
require.NotNil(apk) require.NotNil(apk)
epk, ok := apk.(crypto.PubKeyEd25519) epk, ok := apk.(crypto.PubKeyEd25519)
if assert.True(ok) { 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) { func TestParseGenesisList(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert, require := assert.New(t), require.New(t)
bytes, err := cmn.ReadFile(genesisFilepath) bytes, err := cmn.ReadFile(genesisFilepath)
require.Nil(err, "loading genesis file %+v", err) require.Nil(err, "loading genesis file %+v", err)
// the basecoin genesis go-data :) // the basecoin genesis go-wire/data :)
genDoc := new(FullGenesisDoc) genDoc := new(FullGenesisDoc)
err = json.Unmarshal(bytes, genDoc) err = json.Unmarshal(bytes, genDoc)
require.Nil(err, "unmarshaling genesis file %+v", err) 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: machine:
environment: environment:
GOPATH: /home/ubuntu/.go_workspace GOPATH: "$HOME/.go_workspace"
REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME"
REPO: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME"
PATH: "$GOPATH/bin:$PATH"
hosts: hosts:
circlehost: 127.0.0.1 circlehost: 127.0.0.1
localhost: 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: dependencies:
override: override:
- go get github.com/Masterminds/glide - go get github.com/Masterminds/glide
- go version - go version
- glide --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: test:
override: override:
- "cd $REPO && glide install && go install ./cmd/basecoin"
- ls $GOPATH/bin
- "cd $REPO && make test" - "cd $REPO && make test"
- "cd $REPO/demo && bash start.sh" - "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 package main
import ( import (
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands" "github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
) )
func main() { func main() {
@ -15,6 +18,7 @@ func main() {
RootCmd.AddCommand( RootCmd.AddCommand(
commands.InitCmd, commands.InitCmd,
commands.StartCmd, commands.StartCmd,
commands.RelayCmd,
commands.TxCmd, commands.TxCmd,
commands.QueryCmd, commands.QueryCmd,
commands.KeyCmd, commands.KeyCmd,
@ -25,5 +29,8 @@ func main() {
commands.VersionCmd, 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/basecoin/plugins/ibc"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
tmtypes "github.com/tendermint/tendermint/types" tmtypes "github.com/tendermint/tendermint/types"
) )
@ -196,13 +196,18 @@ func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error {
return err return err
} }
var payload ibc.Payload
if err := wire.ReadBinaryBytes(payloadBytes, &payload); err != nil {
return err
}
ibcTx := ibc.IBCPacketCreateTx{ ibcTx := ibc.IBCPacketCreateTx{
Packet: ibc.Packet{ Packet: ibc.Packet{
SrcChainID: fromChain, SrcChainID: fromChain,
DstChainID: toChain, DstChainID: toChain,
Sequence: sequence, Sequence: sequence,
Type: packetType, Type: packetType,
Payload: payloadBytes, Payload: payload,
}, },
} }
@ -229,7 +234,7 @@ func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error {
} }
var packet ibc.Packet var packet ibc.Packet
proof := new(merkle.IAVLProof) proof := new(iavl.IAVLProof)
err = wire.ReadBinaryBytes(packetBytes, &packet) err = wire.ReadBinaryBytes(packetBytes, &packet)
if err != nil { if err != nil {

View File

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

View File

@ -10,8 +10,10 @@ import (
//"github.com/pkg/errors" //"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
"github.com/tendermint/tmlibs/cli"
) )
//commands //commands
@ -63,8 +65,8 @@ func (a *Address) UnmarshalJSON(addrHex []byte) error {
type Key struct { type Key struct {
Address Address `json:"address"` Address Address `json:"address"`
PubKey crypto.PubKeyS `json:"pub_key"` PubKey crypto.PubKey `json:"pub_key"`
PrivKey crypto.PrivKeyS `json:"priv_key"` PrivKey crypto.PrivKey `json:"priv_key"`
} }
// Implements Signer // Implements Signer
@ -75,18 +77,25 @@ func (k *Key) Sign(msg []byte) crypto.Signature {
// Generates a new validator with private key. // Generates a new validator with private key.
func genKey() *Key { func genKey() *Key {
privKey := crypto.GenPrivKeyEd25519() privKey := crypto.GenPrivKeyEd25519()
addrBytes := privKey.PubKey().Address() pubKey := privKey.PubKey()
addrBytes := pubKey.Address()
var addr Address var addr Address
copy(addr[:], addrBytes) copy(addr[:], addrBytes)
return &Key{ return &Key{
Address: addr, Address: addr,
PubKey: crypto.PubKeyS{privKey.PubKey()}, PubKey: pubKey,
PrivKey: crypto.PrivKeyS{privKey}, PrivKey: privKey.Wrap(),
} }
} }
func LoadKey(keyFile string) (*Key, error) { 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) keyJSONBytes, err := ioutil.ReadFile(filePath)
if err != nil { if err != nil {
return nil, err 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/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
"github.com/tendermint/merkleeyes/iavl"
"github.com/tendermint/tendermint/rpc/client"
tmtypes "github.com/tendermint/tendermint/types" 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) 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 { if err != nil {
return err 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) 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 { if err != nil {
return errors.Errorf("Error unmarshalling proof: %v\n", err) 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" "github.com/spf13/cobra"
tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
tmcfg "github.com/tendermint/tendermint/config/tendermint"
) )
var UnsafeResetAllCmd = &cobra.Command{ var UnsafeResetAllCmd = &cobra.Command{
@ -14,8 +13,10 @@ var UnsafeResetAllCmd = &cobra.Command{
} }
func unsafeResetAllCmd(cmd *cobra.Command, args []string) error { func unsafeResetAllCmd(cmd *cobra.Command, args []string) error {
basecoinDir := BasecoinRoot("") cfg, err := getTendermintConfig()
tmConfig := tmcfg.GetConfig(basecoinDir) if err != nil {
tmcmd.ResetAll(tmConfig, log) return err
}
tmcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), logger)
return nil 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/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/tendermint/abci/server" "github.com/tendermint/abci/server"
cmn "github.com/tendermint/go-common"
eyes "github.com/tendermint/merkleeyes/client" 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/node"
"github.com/tendermint/tendermint/proxy" "github.com/tendermint/tendermint/proxy"
tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/tendermint/types"
"github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/app"
) )
@ -49,12 +52,12 @@ func init() {
} }
func startCmd(cmd *cobra.Command, args []string) error { func startCmd(cmd *cobra.Command, args []string) error {
basecoinDir := BasecoinRoot("") rootDir := viper.GetString(cli.HomeFlag)
// Connect to MerkleEyes // Connect to MerkleEyes
var eyesCli *eyes.Client var eyesCli *eyes.Client
if eyesFlag == "local" { if eyesFlag == "local" {
eyesCli = eyes.NewLocalClient(path.Join(basecoinDir, "data", "merkleeyes.db"), EyesCacheSize) eyesCli = eyes.NewLocalClient(path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize)
} else { } else {
var err error var err error
eyesCli, err = eyes.NewClient(eyesFlag) eyesCli, err = eyes.NewClient(eyesFlag)
@ -65,6 +68,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
// Create Basecoin app // Create Basecoin app
basecoinApp := app.NewBasecoin(eyesCli) basecoinApp := app.NewBasecoin(eyesCli)
basecoinApp.SetLogger(logger.With("module", "app"))
// register IBC plugn // register IBC plugn
basecoinApp.RegisterPlugin(NewIBCPlugin()) basecoinApp.RegisterPlugin(NewIBCPlugin())
@ -78,7 +82,7 @@ func startCmd(cmd *cobra.Command, args []string) error {
// else, assume it's been loaded // else, assume it's been loaded
if basecoinApp.GetState().GetChainID() == "" { if basecoinApp.GetState().GetChainID() == "" {
// If genesis file exists, set key-value options // 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 { if _, err := os.Stat(genesisFile); err == nil {
err := basecoinApp.LoadGenesis(genesisFile) err := basecoinApp.LoadGenesis(genesisFile)
if err != nil { if err != nil {
@ -91,23 +95,24 @@ func startCmd(cmd *cobra.Command, args []string) error {
chainID := basecoinApp.GetState().GetChainID() chainID := basecoinApp.GetState().GetChainID()
if withoutTendermintFlag { 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 // run just the abci app/server
return startBasecoinABCI(basecoinApp) return startBasecoinABCI(basecoinApp)
} else { } 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 // start the app with tendermint in-process
return startTendermint(basecoinDir, basecoinApp) return startTendermint(rootDir, basecoinApp)
} }
} }
func startBasecoinABCI(basecoinApp *app.Basecoin) error { func startBasecoinABCI(basecoinApp *app.Basecoin) error {
// Start the ABCI listener // Start the ABCI listener
svr, err := server.NewServer(addrFlag, "socket", basecoinApp) svr, err := server.NewServer(addrFlag, "socket", basecoinApp)
if err != nil { if err != nil {
return errors.Errorf("Error creating listener: %v\n", err) return errors.Errorf("Error creating listener: %v\n", err)
} }
svr.SetLogger(logger.With("module", "abci-server"))
svr.Start()
// Wait forever // Wait forever
cmn.TrapSignal(func() { cmn.TrapSignal(func() {
@ -117,27 +122,41 @@ func startBasecoinABCI(basecoinApp *app.Basecoin) error {
return nil 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 { func startTendermint(dir string, basecoinApp *app.Basecoin) error {
cfg, err := getTendermintConfig()
// 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()
if err != nil { if err != nil {
return err return err
} }
// Wait forever // TODO: parse the log level from the config properly (multi modules)
cmn.TrapSignal(func() { // but some tm code must be refactored for better usability
// Cleanup lvl, err := log.AllowLevel(cfg.LogLevel)
n.Stop() 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 return nil
} }

View File

@ -3,16 +3,15 @@ package commands
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"strings"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
client "github.com/tendermint/go-rpc/client"
wire "github.com/tendermint/go-wire" wire "github.com/tendermint/go-wire"
ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/rpc/client"
) )
//commands //commands
@ -60,7 +59,7 @@ func init() {
{&fromFlag, "from", "key.json", "Path to a private key to sign the transaction"}, {&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)"}, {&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"}, {&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)"}, {&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 { 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 // convert destination address to bytes
to, err := hex.DecodeString(StripHex(toFlag)) to, err := hex.DecodeString(StripHex(toHex))
if err != nil { if err != nil {
return errors.Errorf("To address is invalid hex: %v\n", err) return errors.Errorf("To address is invalid hex: %v\n", err)
} }
if chainPrefix != "" {
to = []byte(chainPrefix + "/" + string(to))
}
// load the priv key // load the priv key
privKey, err := LoadKey(fromFlag) privKey, err := LoadKey(fromFlag)
if err != nil { if err != nil {
@ -123,7 +139,7 @@ func sendTxCmd(cmd *cobra.Command, args []string) error {
// sign that puppy // sign that puppy
signBytes := tx.SignBytes(chainIDFlag) 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("Signed SendTx:")
fmt.Println(string(wire.JSONBytes(tx))) fmt.Println(string(wire.JSONBytes(tx)))
@ -179,7 +195,7 @@ func AppTx(name string, data []byte) error {
Data: data, 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("Signed AppTx:")
fmt.Println(string(wire.JSONBytes(tx))) fmt.Println(string(wire.JSONBytes(tx)))
@ -194,23 +210,17 @@ func AppTx(name string, data []byte) error {
// broadcast the transaction to tendermint // broadcast the transaction to tendermint
func broadcastTx(tx types.Tx) ([]byte, string, error) { func broadcastTx(tx types.Tx) ([]byte, string, error) {
httpClient := client.NewHTTP(txNodeFlag, "/websocket")
tmResult := new(ctypes.TMResult)
uriClient := client.NewURIClient(txNodeFlag)
// Don't you hate having to do this? // Don't you hate having to do this?
// How many times have I lost an hour over this trick?! // How many times have I lost an hour over this trick?!
txBytes := []byte(wire.BinaryBytes(struct { txBytes := []byte(wire.BinaryBytes(struct {
types.Tx `json:"unwrap"` types.Tx `json:"unwrap"`
}{tx})) }{tx}))
res, err := httpClient.BroadcastTxCommit(txBytes)
_, err := uriClient.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult)
if err != nil { if err != nil {
return nil, "", errors.Errorf("Error on broadcast tx: %v", err) 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 it fails check, we don't even get a delivertx back!
if !res.CheckTx.Code.IsOK() { if !res.CheckTx.Code.IsOK() {
r := res.CheckTx r := res.CheckTx
@ -228,12 +238,12 @@ func broadcastTx(tx types.Tx) ([]byte, string, error) {
// if the sequence flag is set, return it; // if the sequence flag is set, return it;
// else, fetch the account by querying the app and return the sequence number // else, fetch the account by querying the app and return the sequence number
func getSeq(address []byte) (int, error) { func getSeq(address []byte) (int, error) {
if seqFlag >= 0 { if seqFlag >= 0 {
return seqFlag, nil return seqFlag, nil
} }
acc, err := getAcc(txNodeFlag, address) httpClient := client.NewHTTP(txNodeFlag, "/websocket")
acc, err := getAccWithClient(httpClient, address)
if err != nil { if err != nil {
return 0, err return 0, err
} }

View File

@ -3,52 +3,20 @@ package commands
import ( import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"os"
"path"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/pflag" "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" "github.com/tendermint/basecoin/types"
abci "github.com/tendermint/abci/types" client "github.com/tendermint/tendermint/rpc/client"
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"
tmtypes "github.com/tendermint/tendermint/types" 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 //Quickly registering flags can be quickly achieved through using the utility functions
//RegisterFlags, and RegisterPersistentFlags. Ex: //RegisterFlags, and RegisterPersistentFlags. Ex:
// flags := []Flag2Register{ // flags := []Flag2Register{
@ -130,31 +98,27 @@ func StripHex(s string) string {
return s return s
} }
func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) {
uriClient := client.NewURIClient(tmAddr) httpClient := client.NewHTTP(tmAddr, "/websocket")
tmResult := new(ctypes.TMResult) 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 { if err != nil {
return nil, errors.Errorf("Error calling /abci_query: %v", err) return nil, errors.Errorf("Error calling /abci_query: %v", err)
} }
res := (*tmResult).(*ctypes.ResultABCIQuery) if !res.Code.IsOK() {
if !res.Response.Code.IsOK() { return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Code, res.Log)
return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log)
} }
return &res.Response, nil return res.ResultQuery, nil
} }
// fetch the account by querying the app // 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) key := types.AccountKey(address)
response, err := Query(tmAddr, key) response, err := queryWithClient(httpClient, key)
if err != nil { if err != nil {
return nil, err 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) { func getHeaderAndCommit(tmAddr string, height int) (*tmtypes.Header, *tmtypes.Commit, error) {
tmResult := new(ctypes.TMResult) httpClient := client.NewHTTP(tmAddr, "/websocket")
uriClient := client.NewURIClient(tmAddr) res, err := httpClient.Commit(height)
method := "commit"
_, err := uriClient.Call(method, map[string]interface{}{"height": height}, tmResult)
if err != nil { 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 := res.Header
header := resCommit.Header commit := res.Commit
commit := resCommit.Commit
return header, commit, nil 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 package main
import ( import (
"os"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/tendermint/basecoin/cmd/commands" "github.com/tendermint/basecoin/cmd/commands"
"github.com/tendermint/tmlibs/cli"
) )
func main() { func main() {
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
Use: "counter", Use: "counter",
Short: "demo plugin for basecoin", Short: "demo plugin for basecoin",
} }
RootCmd.AddCommand( RootCmd.AddCommand(
commands.InitCmd,
commands.StartCmd, commands.StartCmd,
commands.TxCmd, commands.TxCmd,
commands.QueryCmd, commands.QueryCmd,
@ -21,8 +24,10 @@ func main() {
commands.VerifyCmd, commands.VerifyCmd,
commands.BlockCmd, commands.BlockCmd,
commands.AccountCmd, 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 = "" seeds = ""
fast_sync = true fast_sync = true
db_backend = "leveldb" db_backend = "leveldb"
log_level = "notice" log_level = "info"
rpc_laddr = "tcp://0.0.0.0:46657" rpc_laddr = "tcp://0.0.0.0:46657"

View File

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

View File

@ -1,16 +1 @@
{ {"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"}}
"address": "EBB0B4A899973C524A6BB18A161056A55F590F41",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"5FFDC1EA5FA2CA4A0A5503C86D2D348C5B401AD80FAA1899508F1ED00D8982E8D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
],
"pub_key": [
1,
"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A"
]
}

View File

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

View File

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

View File

@ -1,16 +1 @@
{ {"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"}}
"address": "D42CFCB9C42DF9A73143EEA89255D1DF027B6240",
"last_height": 0,
"last_round": 0,
"last_signature": null,
"last_signbytes": "",
"last_step": 0,
"priv_key": [
1,
"6353FAF4ADEB03EA496A9EAE5BE56C4C6A851CB705401788184FDC9198413C2C9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
],
"pub_key": [
1,
"9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F"
]
}

View File

@ -4,7 +4,8 @@ set -e
cd $GOPATH/src/github.com/tendermint/basecoin/demo cd $GOPATH/src/github.com/tendermint/basecoin/demo
LOG_DIR="." LOG_DIR="."
TM_VERSION="v0.9.2" TM_VERSION="develop"
#TM_VERSION="v0.10.0"
if [[ "$CIRCLECI" == "true" ]]; then if [[ "$CIRCLECI" == "true" ]]; then
# set log dir # set log dir
@ -23,6 +24,13 @@ fi
set -u set -u
function ifExit() {
if [[ "$?" != 0 ]]; then
echo "FAIL"
exit 1
fi
}
function removeQuotes() { function removeQuotes() {
temp="${1%\"}" temp="${1%\"}"
temp="${temp#\"}" temp="${temp#\"}"
@ -52,12 +60,12 @@ function waitForNode() {
function waitForBlock() { function waitForBlock() {
addr=$1 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 b2=$b1
while [ "$b2" == "$b1" ]; do while [ "$b2" == "$b1" ]; do
echo "Waiting for node $addr to commit a block ..." echo "Waiting for node $addr to commit a block ..."
sleep 1 sleep 1
b2=`curl -s $addr/status | jq .result[1].latest_block_height` b2=`curl -s $addr/status | jq .result.latest_block_height`
done done
} }
@ -83,12 +91,16 @@ echo ""
echo "... starting chains" echo "... starting chains"
echo "" echo ""
# start the first node # 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 & BCHOME=$BCHOME1 basecoin start --without-tendermint &> $LOG_DIR/chain1_basecoin.log &
ifExit
# start the second node # 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 & BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> $LOG_DIR/chain2_basecoin.log &
ifExit
echo "" echo ""
echo "... waiting for chains to start" echo "... waiting for chains to start"
@ -105,19 +117,26 @@ echo "... registering chain1 on chain2"
echo "" echo ""
# register chain1 on chain2 # register chain1 on chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json
ifExit
echo "" echo ""
echo "... creating egress packet on chain1" echo "... creating egress packet on chain1"
echo "" echo ""
# create a packet on chain1 destined for chain2 # send coins from chain1 to an address on chain2
PAYLOAD="DEADBEEF" #TODO # TODO: dont hardcode the address
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 1 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 ""
echo "... querying for packet data" echo "... querying for packet data"
echo "" echo ""
# query for the packet data and proof # query for the packet data and proof
QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) # 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) HEIGHT=$(echo $QUERY_RESULT | jq .height)
PACKET=$(echo $QUERY_RESULT | jq .value) PACKET=$(echo $QUERY_RESULT | jq .value)
PROOF=$(echo $QUERY_RESULT | jq .proof) PROOF=$(echo $QUERY_RESULT | jq .proof)
@ -144,6 +163,7 @@ echo "... querying for block data"
echo "" echo ""
# get the header and commit for the height # get the header and commit for the height
HEADER_AND_COMMIT=$(basecoin block $HEIGHT) HEADER_AND_COMMIT=$(basecoin block $HEIGHT)
ifExit
HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header)
HEADER=$(removeQuotes $HEADER) HEADER=$(removeQuotes $HEADER)
COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit)
@ -158,18 +178,21 @@ echo "... updating state of chain1 on chain2"
echo "" echo ""
# update the state of chain1 on chain2 # update the state of chain1 on chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT
ifExit
echo "" echo ""
echo "... posting packet from chain1 on chain2" echo "... posting packet from chain1 on chain2"
echo "" echo ""
# post the packet from chain1 to chain2 # post the packet from chain1 to chain2
basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF
ifExit
echo "" echo ""
echo "... checking if the packet is present on chain2" echo "... checking if the packet is present on chain2"
echo "" echo ""
# query for the packet on chain2 # query for the packet on chain2
basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,0
ifExit
echo "" echo ""
echo "DONE!" echo "DONE!"

View File

@ -98,7 +98,7 @@ make install
make test 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. 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 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. 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. 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, 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). 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 ## 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, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent,
and the `GasPrice` is implicit. and the `GasPrice` is implicit.
In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering 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 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 plugin to control its execution. There is currently no means to pass `Fee` information
to the Tendermint validators, but it will come soon... 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. 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`, 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. 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 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 # The Basecoin Tool
In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guides/basecoin-basics) In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guide/basecoin-basics.md)
and [how to implement a plugin](/docs/guides/example-plugin). and [how to implement a plugin](/docs/guide/basecoin-plugins.md).
In this tutorial, we provide more details on using the `basecoin` tool. In this tutorial, we provide more details on using the `basecoin` tool.
# Data Directory # Data Directory

View File

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

View File

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

178
glide.lock generated
View File

@ -1,39 +1,92 @@
hash: c6e5febc35b5fd1003066820defb8a089db048b407239dad9faf44553fdc15e8 hash: 9d06ae13959cbb2835f5ae400a4b65e4bc329a567c949aec4aeab318c271da39
updated: 2017-04-21T12:55:42.7004558-04:00 updated: 2017-05-24T15:11:32.643553723+02:00
imports: imports:
- name: github.com/bgentry/speakeasy
version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd
- name: github.com/btcsuite/btcd - name: github.com/btcsuite/btcd
version: 4b348c1d33373d672edd83fc576892d0e46686d2 version: b8df516b4b267acf2de46be593a9d948d1d2c420
subpackages: subpackages:
- btcec - btcec
- name: github.com/btcsuite/fastsha256
version: 637e656429416087660c84436a2a035d69d54e2e
- name: github.com/BurntSushi/toml - name: github.com/BurntSushi/toml
version: b26d9c308763d68093482582cea63d69be07a0f0 version: b26d9c308763d68093482582cea63d69be07a0f0
- name: github.com/ebuchman/fail-test - name: github.com/ebuchman/fail-test
version: 95f809107225be108efcf10a3509e4ea6ceef3c4 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 - name: github.com/go-stack/stack
version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82
- name: github.com/golang/protobuf - name: github.com/golang/protobuf
version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef version: 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8
subpackages: subpackages:
- proto - proto
- ptypes/any - ptypes/any
- name: github.com/golang/snappy - name: github.com/golang/snappy
version: 553a641470496b2327abcac10b36396bd98e45c9 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 - 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 - name: github.com/inconshreveable/mousetrap
version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75
- name: github.com/jmhodges/levigo - name: github.com/jmhodges/levigo
version: c42d9e0ca023e2198120196f842701bb4c55d7b9 version: c42d9e0ca023e2198120196f842701bb4c55d7b9
- name: github.com/mattn/go-colorable - name: github.com/kr/logfmt
version: ded68f7a9561c023e790de24279db7ebf473ea80 version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0
- name: github.com/mattn/go-isatty - name: github.com/magiconair/properties
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe 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 - name: github.com/pkg/errors
version: ff09b135c25aae272398c51a07235b90a75aa4f0 version: ff09b135c25aae272398c51a07235b90a75aa4f0
- name: github.com/spf13/afero
version: 9be650865eab0c12963d8753212f4f9c66cdcf12
subpackages:
- mem
- name: github.com/spf13/cast
version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4
- name: github.com/spf13/cobra - name: github.com/spf13/cobra
version: 10f6b9d7e1631a54ad07c5c0fb71c28a1abfd3c2 version: 3454e0e28e69c1b8effa6b5123c8e4185e20d696
- name: github.com/spf13/jwalterweatherman
version: 8f07c835e5cc1450c082fe3a439cf87b0cbb2d99
- name: github.com/spf13/pflag - name: github.com/spf13/pflag
version: 2300d0f8576fe575f71aaa5b9bbe4e1b0dc2eb51 version: e57e3eeb33f795204c1ca35f56c44f83227c6e66
- name: github.com/spf13/viper
version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2
- name: github.com/syndtr/goleveldb - name: github.com/syndtr/goleveldb
version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4
subpackages: subpackages:
@ -50,7 +103,7 @@ imports:
- leveldb/table - leveldb/table
- leveldb/util - leveldb/util
- name: github.com/tendermint/abci - name: github.com/tendermint/abci
version: 56e13d87f4e3ec1ea756957d6b23caa6ebcf0998 version: 5dabeffb35c027d7087a12149685daa68989168b
subpackages: subpackages:
- client - client
- example/dummy - example/dummy
@ -61,72 +114,80 @@ imports:
subpackages: subpackages:
- edwards25519 - edwards25519
- extra25519 - 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 - name: github.com/tendermint/go-crypto
version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da version: 438b16f1f84ef002d7408ecd6fc3a3974cbc9559
- 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
subpackages: subpackages:
- flowrate - cmd
- name: github.com/tendermint/go-logger - keys
version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 - keys/cryptostore
- name: github.com/tendermint/go-merkle - keys/server
version: 714d4d04557fd068a7c2a1748241ce8428015a96 - keys/server/types
- name: github.com/tendermint/go-p2p - keys/storage/filestorage
version: 17124989a93774833df33107fbf17157a7f8ef31
subpackages:
- upnp
- name: github.com/tendermint/go-rpc
version: 559613689d56eaa423b19a3a4158546beb4857de
subpackages:
- client
- server
- types
- name: github.com/tendermint/go-wire - name: github.com/tendermint/go-wire
version: c1c9a57ab8038448ddea1714c0698f8051e5748c version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74
- name: github.com/tendermint/log15
version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6
subpackages: 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 - name: github.com/tendermint/merkleeyes
version: 9fb76efa5aebe773a598f97e68e75fe53d520e70 version: c722818b460381bc5b82e38c73ff6e22a9df624d
subpackages: subpackages:
- app - app
- client - client
- iavl
- name: github.com/tendermint/tendermint - name: github.com/tendermint/tendermint
version: 6bcd4242f1f336e2b2ef4f644fabaf56222b34d0 version: 11b5d11e9eec170e1d3dce165f0270d5c0759d69
subpackages: subpackages:
- blockchain - blockchain
- cmd/tendermint/commands - cmd/tendermint/commands
- config/tendermint - cmd/tendermint/commands/flags
- config
- consensus - consensus
- mempool - mempool
- node - node
- p2p
- p2p/upnp
- proxy - proxy
- rpc/client
- rpc/core - rpc/core
- rpc/core/types - rpc/core/types
- rpc/grpc - rpc/grpc
- rpc/lib
- rpc/lib/client
- rpc/lib/server
- rpc/lib/types
- state - state
- state/txindex - state/txindex
- state/txindex/kv - state/txindex/kv
- state/txindex/null - state/txindex/null
- types - types
- version - version
- name: github.com/tendermint/tmlibs
version: 8af1c70a8be17543eb33e9bfbbcdd8371e3201cc
subpackages:
- autofile
- cli
- clist
- common
- db
- events
- flowrate
- log
- logger
- merkle
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 96846453c37f0876340a66a47f3f75b1f3a6cd2d version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e
subpackages: subpackages:
- curve25519 - curve25519
- nacl/box - nacl/box
@ -137,7 +198,7 @@ imports:
- ripemd160 - ripemd160
- salsa20/salsa - salsa20/salsa
- name: golang.org/x/net - name: golang.org/x/net
version: c8c74377599bd978aee1cf3b9b63a8634051cec2 version: feeb485667d1fdabe727840fe00adc22431bc86e
subpackages: subpackages:
- context - context
- http2 - http2
@ -147,11 +208,11 @@ imports:
- lex/httplex - lex/httplex
- trace - trace
- name: golang.org/x/sys - name: golang.org/x/sys
version: ea9bcade75cb975a0b9738936568ab388b845617 version: 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03
subpackages: subpackages:
- unix - unix
- name: golang.org/x/text - name: golang.org/x/text
version: 19e3104b43db45fca0303f489a9536087b184802 version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4
subpackages: subpackages:
- secure/bidirule - secure/bidirule
- transform - transform
@ -162,10 +223,11 @@ imports:
subpackages: subpackages:
- googleapis/rpc/status - googleapis/rpc/status
- name: google.golang.org/grpc - name: google.golang.org/grpc
version: 6914ab1e338c92da4218a23d27fcd03d0ad78d46 version: 844f573616520565fdc6fb4db242321b5456fd6d
subpackages: subpackages:
- codes - codes
- credentials - credentials
- grpclb/grpc_lb_v1
- grpclog - grpclog
- internal - internal
- keepalive - keepalive
@ -176,6 +238,10 @@ imports:
- status - status
- tap - tap
- transport - transport
- name: gopkg.in/go-playground/validator.v9
version: 6d8c18553ea1ac493d049edd6f102f52e618f085
- name: gopkg.in/yaml.v2
version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b
testImports: testImports:
- name: github.com/davecgh/go-spew - name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9

View File

@ -1,24 +1,53 @@
package: github.com/tendermint/basecoin package: github.com/tendermint/basecoin
import: import:
- package: github.com/tendermint/go-common - package: github.com/gorilla/websocket
version: develop - 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 - package: github.com/tendermint/go-crypto
version: develop subpackages:
- package: github.com/tendermint/go-events - cmd
version: develop - keys
- package: github.com/tendermint/go-logger
version: develop
- package: github.com/tendermint/go-data
version: develop
- package: github.com/tendermint/go-rpc
version: develop
- package: github.com/tendermint/go-wire - package: github.com/tendermint/go-wire
subpackages:
- data
- package: github.com/tendermint/light-client
version: develop version: develop
subpackages:
- commands
- commands/proofs
- commands/seeds
- commands/txs
- proofs
- package: github.com/tendermint/merkleeyes - package: github.com/tendermint/merkleeyes
version: develop subpackages:
- client
- iavl
- package: github.com/tendermint/tendermint - package: github.com/tendermint/tendermint
version: develop version: develop
- package: github.com/tendermint/abci subpackages:
version: develop - config
- package: github.com/gorilla/websocket - node
version: v1.1.0 - 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" abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/app"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
eyescli "github.com/tendermint/merkleeyes/client" eyescli "github.com/tendermint/merkleeyes/client"
) )
@ -52,8 +51,7 @@ func TestCounterPlugin(t *testing.T) {
// Sign request // Sign request
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
// t.Logf("Sign bytes: %X\n", signBytes) // t.Logf("Sign bytes: %X\n", signBytes)
sig := test1PrivAcc.Sign(signBytes) tx.Input.Signature = test1PrivAcc.Sign(signBytes)
tx.Input.Signature = crypto.SignatureS{sig}
// t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx})) // t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx}))
// Write request // Write request

View File

@ -2,15 +2,19 @@ package ibc
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt"
"net/url" "net/url"
"strconv"
"strings" "strings"
abci "github.com/tendermint/abci/types" 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" "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" tm "github.com/tendermint/tendermint/types"
) )
@ -51,8 +55,108 @@ type Packet struct {
SrcChainID string SrcChainID string
DstChainID string DstChainID string
Sequence uint64 Sequence uint64
Type string Type string // redundant now that Type() is a method on Payload ?
Payload []byte 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 chainGen := tx.BlockchainGenesis
// Parse genesis // Parse genesis
var chainGenDoc = &tm.GenesisDoc{} chainGenDoc := new(tm.GenesisDoc)
var err error err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc)
wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err)
if err != nil { if err != nil {
sm.res.Code = IBCCodeEncodingError sm.res.Code = IBCCodeEncodingError
sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error() 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" sm.res.Log = "Already exists"
return 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 new Packet
save(sm.store, packetKey, 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) { func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
@ -326,7 +449,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
return return
} }
// Save new Packet // Save new Packet (just for fun)
save(sm.store, packetKeyIngress, packet) save(sm.store, packetKeyIngress, packet)
// Load Header and make sure it exists // Load Header and make sure it exists
@ -355,10 +478,24 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) {
ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash)
if !ok { if !ok {
sm.res.Code = IBCCodeInvalidProof 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 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 return
} }

View File

@ -2,18 +2,22 @@ package ibc
import ( import (
"bytes" "bytes"
"encoding/json"
"sort" "sort"
"strings" "strings"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types"
cmn "github.com/tendermint/go-common"
crypto "github.com/tendermint/go-crypto" crypto "github.com/tendermint/go-crypto"
"github.com/tendermint/go-merkle"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client" 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" 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) name := cmn.Fmt("%v_val_%v", chainID, i)
privAcc := types.PrivAccountFromSecret(name) privAcc := types.PrivAccountFromSecret(name)
genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{ genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{
PubKey: privAcc.PubKey.PubKey, PubKey: privAcc.PubKey,
Amount: 1, Amount: 1,
Name: name, Name: name,
}) })
@ -64,23 +68,65 @@ func (pas PrivAccountsByAddress) Swap(i, j int) {
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
func TestIBCPlugin(t *testing.T) { var testGenesisDoc = `{
assert := assert.New(t) "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) eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient) store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity store.SetLogging() // Log all activity
ibcPlugin := New() ibcPlugin := New()
ctx := types.CallContext{ ctx := types.NewCallContext(nil, nil, types.Coins{})
CallerAddress: nil,
CallerAccount: nil,
Coins: types.Coins{},
}
chainID_1 := "test_chain" chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) genDoc_1, _ := genGenesisDoc(chainID_1, 4)
genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) genDocJSON_1, err := json.Marshal(genDoc_1)
require.Nil(err)
// Register a malformed chain // Register a malformed chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
@ -89,20 +135,10 @@ func TestIBCPlugin(t *testing.T) {
Genesis: "<THIS IS NOT JSON>", Genesis: "<THIS IS NOT JSON>",
}, },
}})) }}))
assert.Equal(IBCCodeEncodingError, res.Code) assertAndLog(t, store, res, IBCCodeEncodingError)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Successfully register a chain // Successfully register a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
BlockchainGenesis{
ChainID: "test_chain",
Genesis: string(genDocJSON_1),
},
}}))
assert.True(res.IsOK(), res.Log)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Duplicate request fails // Duplicate request fails
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{
@ -111,74 +147,82 @@ func TestIBCPlugin(t *testing.T) {
Genesis: string(genDocJSON_1), Genesis: string(genDocJSON_1),
}, },
}})) }}))
assert.Equal(IBCCodeChainAlreadyExists, res.Code, res.Log) assertAndLog(t, store, res, IBCCodeChainAlreadyExists)
t.Log(">>", strings.Join(store.GetLogLines(), "\n")) }
store.ClearLogLines()
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) // Create a new packet (for testing)
packet := Packet{ packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world")))
SrcChainID: "test_chain", res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
DstChainID: "dst_chain",
Sequence: 0,
Type: "data",
Payload: []byte("hello world"),
}
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet, Packet: packet,
}})) }}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log) assertAndLog(t, store, res, abci.CodeType_OK)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Post a duplicate packet // Post a duplicate packet
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet, Packet: packet,
}})) }}))
assert.Equal(IBCCodePacketAlreadyExists, res.Code, res.Log) assertAndLog(t, store, res, IBCCodePacketAlreadyExists)
t.Log(">>", strings.Join(store.GetLogLines(), "\n")) }
store.ClearLogLines()
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. // Construct a Header that includes the above packet.
store.Sync() store.Sync()
resCommit := eyesClient.CommitSync() resCommit := eyesClient.CommitSync()
appHash := resCommit.Data appHash := resCommit.Data
header := tm.Header{ header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
ChainID: "test_chain",
Height: 999,
AppHash: appHash,
ValidatorsHash: []byte("must_exist"), // TODO make optional
}
// Construct a Commit that signs above header // Construct a Commit that signs above header
blockHash := header.Hash() commit := constructCommit(privAccs_1, header)
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 // Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header, Header: header,
Commit: commit, Commit: commit,
}})) }}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log) assertAndLog(t, store, res, abci.CodeType_OK)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Get proof for the packet // Get proof for the packet
packetKey := toKey(_IBC, _EGRESS, packetKey := toKey(_IBC, _EGRESS,
@ -192,7 +236,7 @@ func TestIBCPlugin(t *testing.T) {
Prove: true, Prove: true,
}) })
assert.Nil(err) assert.Nil(err)
var proof *merkle.IAVLProof var proof *iavl.IAVLProof
err = wire.ReadBinaryBytes(resQuery.Proof, &proof) err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(err) assert.Nil(err)
@ -203,169 +247,74 @@ func TestIBCPlugin(t *testing.T) {
Packet: packet, Packet: packet,
Proof: proof, Proof: proof,
}})) }}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log) assertAndLog(t, store, res, abci.CodeType_OK)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
} }
func TestIBCPluginBadCommit(t *testing.T) { func TestIBCPluginPayloadCoins(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
require := require.New(t)
eyesClient := eyes.NewLocalClient("", 0) eyesClient := eyes.NewLocalClient("", 0)
store := types.NewKVCache(eyesClient) store := types.NewKVCache(eyesClient)
store.SetLogging() // Log all activity store.SetLogging() // Log all activity
ibcPlugin := New() ibcPlugin := New()
ctx := types.CallContext{ coins := types.Coins{
CallerAddress: nil, types.Coin{
CallerAccount: nil, Denom: "mycoin",
Coins: types.Coins{}, Amount: 100,
},
} }
ctx := types.NewCallContext(nil, nil, coins)
chainID_1 := "test_chain" chainID_1 := "test_chain"
genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) 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 // Register a chain
res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1))
BlockchainGenesis{
ChainID: "test_chain", // send coins to this addr on the other chain
Genesis: string(genDocJSON_1), 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) assertAndLog(t, store, res, abci.CodeType_InsufficientFunds)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Construct a Header // Send a small enough number of coins
header := tm.Header{ packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{
ChainID: "test_chain", Address: destinationAddr,
Height: 999, Coins: coinsGood,
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"),
}
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{
Packet: packet, Packet: packet,
}})) }}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log) assertAndLog(t, store, res, abci.CodeType_OK)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Construct a Header that includes the above packet. // Construct a Header that includes the above packet.
store.Sync() store.Sync()
resCommit := eyesClient.CommitSync() resCommit := eyesClient.CommitSync()
appHash := resCommit.Data appHash := resCommit.Data
header := tm.Header{ header := newHeader("test_chain", 999, appHash, []byte("must_exist"))
ChainID: "test_chain",
Height: 999,
AppHash: appHash,
ValidatorsHash: []byte("must_exist"), // TODO make optional
}
// Construct a Commit that signs above header // Construct a Commit that signs above header
blockHash := header.Hash() commit := constructCommit(privAccs_1, header)
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 // Update a chain
res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{
Header: header, Header: header,
Commit: commit, Commit: commit,
}})) }}))
assert.Equal(abci.CodeType_OK, res.Code, res.Log) assertAndLog(t, store, res, abci.CodeType_OK)
t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines()
// Get proof for the packet // Get proof for the packet
packetKey := toKey(_IBC, _EGRESS, packetKey := toKey(_IBC, _EGRESS,
@ -379,7 +328,120 @@ func TestIBCPluginBadProof(t *testing.T) {
Prove: true, Prove: true,
}) })
assert.Nil(err) 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) err = wire.ReadBinaryBytes(resQuery.Proof, &proof)
assert.Nil(err) assert.Nil(err)
@ -393,7 +455,58 @@ func TestIBCPluginBadProof(t *testing.T) {
Packet: packet, Packet: packet,
Proof: proof, 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")) t.Log(">>", strings.Join(store.GetLogLines(), "\n"))
store.ClearLogLines() 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" "time"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
cmn "github.com/tendermint/go-common" cmn "github.com/tendermint/tmlibs/common"
"github.com/tendermint/go-rpc/client" "github.com/tendermint/tendermint/rpc/lib/client"
"github.com/tendermint/go-rpc/types" "github.com/tendermint/tendermint/rpc/lib/types"
"github.com/tendermint/go-wire" "github.com/tendermint/go-wire"
_ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types _ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types
) )

View File

@ -2,14 +2,15 @@ package state
import ( import (
abci "github.com/tendermint/abci/types" 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" "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. // 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 { func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) abci.Result {
chainID := state.GetChainID() chainID := state.GetChainID()
// Exec tx // Exec tx
@ -95,11 +96,11 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
res = validateInputAdvanced(inAcc, signBytes, tx.Input) res = validateInputAdvanced(inAcc, signBytes, tx.Input)
if res.IsErr() { 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()") return res.PrependLog("in validateInputAdvanced()")
} }
if !tx.Input.Coins.IsGTE(types.Coins{tx.Fee}) { 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})) 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) res = plugin.RunTx(cache, ctx, tx.Data)
if res.IsOK() { if res.IsOK() {
cache.CacheSync() cache.CacheSync()
log.Info("Successful execution") state.logger.Info("Successful execution")
// Fire events // Fire events
/* /*
if evc != nil { if evc != nil {
@ -144,7 +145,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e
} }
*/ */
} else { } else {
log.Info("AppTx failed", "error", res) state.logger.Info("AppTx failed", "error", res)
// Just return the coins and return. // Just return the coins and return.
inAccCopy.Balance = inAccCopy.Balance.Plus(coins) inAccCopy.Balance = inAccCopy.Balance.Plus(coins)
// But take the gas // But take the gas
@ -190,17 +191,23 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco
} }
for _, out := range outs { 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 // Account shouldn't be duplicated
if _, ok := accounts[string(out.Address)]; ok { if _, ok := accounts[string(outAddress)]; ok {
return nil, abci.ErrBaseDuplicateAddress return nil, abci.ErrBaseDuplicateAddress
} }
acc := state.GetAccount(out.Address) acc := state.GetAccount(outAddress)
// output account may be nil (new) // output account may be nil (new)
if acc == nil { if acc == nil {
// zero value is valid, empty account // zero value is valid, empty account
acc = &types.Account{} acc = &types.Account{}
} }
accounts[string(out.Address)] = acc accounts[string(outAddress)] = acc
} }
return accounts, abci.OK 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)) return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("balance is %v, tried to send %v", balance, in.Coins))
} }
// Check signatures // 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.ErrBaseInvalidSignature.AppendLog(cmn.Fmt("SignBytes: %X", signBytes))
} }
return abci.OK 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 { 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 { if acc == nil {
cmn.PanicSanity("adjustByOutputs() expects account in accounts") cmn.PanicSanity("adjustByOutputs() expects account in accounts")
} }
acc.Balance = acc.Balance.Plus(out.Coins) acc.Balance = acc.Balance.Plus(out.Coins)
if !isCheckTx { if !isCheckTx {
state.SetAccount(out.Address, acc) state.SetAccount(outAddress, acc)
} }
} }
} }

View File

@ -6,6 +6,9 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/tmlibs/log"
"github.com/tendermint/basecoin/plugins/ibc"
"github.com/tendermint/basecoin/types" "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...) 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 // 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) { 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 initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance
@ -63,6 +61,7 @@ func (et *execTest) reset() {
et.store = types.NewMemKVStore() et.store = types.NewMemKVStore()
et.state = NewState(et.store) et.state = NewState(et.store)
et.state.SetLogger(log.TestingLogger())
et.state.SetChainID(et.chainID) et.state.SetChainID(et.chainID)
// NOTE we dont run acc2State here // NOTE we dont run acc2State here
@ -83,19 +82,19 @@ func TestGetInputs(t *testing.T) {
//test getInputs for registered, non-registered account //test getInputs for registered, non-registered account
et.reset() et.reset()
txs := types.Accs2TxInputs(1, et.accIn) inputs := types.Accs2TxInputs(1, et.accIn)
acc, res = getInputs(et.state, txs) acc, res = getInputs(et.state, inputs)
assert.True(res.IsErr(), "getInputs: expected error when using getInput with non-registered Input") assert.True(res.IsErr(), "getInputs: expected error when using getInput with non-registered Input")
et.acc2State(et.accIn) 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") assert.True(res.IsOK(), "getInputs: expected to getInput from registered Input")
//test sending duplicate accounts //test sending duplicate accounts
et.reset() et.reset()
et.acc2State(et.accIn, et.accIn, et.accIn) et.acc2State(et.accIn, et.accIn, et.accIn)
txs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn) inputs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn)
acc, res = getInputs(et.state, txs) acc, res = getInputs(et.state, inputs)
assert.True(res.IsErr(), "getInputs: expected error when sending duplicate accounts") assert.True(res.IsErr(), "getInputs: expected error when sending duplicate accounts")
} }
@ -110,24 +109,24 @@ func TestGetOrMakeOutputs(t *testing.T) {
//test sending duplicate accounts //test sending duplicate accounts
et.reset() et.reset()
txs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn) outputs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn)
_, res = getOrMakeOutputs(et.state, nil, txs) _, res = getOrMakeOutputs(et.state, nil, outputs)
assert.True(res.IsErr(), "getOrMakeOutputs: expected error when sending duplicate accounts") assert.True(res.IsErr(), "getOrMakeOutputs: expected error when sending duplicate accounts")
//test sending to existing/new account //test sending to existing/new account
et.reset() et.reset()
txs1 := types.Accs2TxOutputs(et.accIn) outputs1 := types.Accs2TxOutputs(et.accIn)
txs2 := types.Accs2TxOutputs(et.accOut) outputs2 := types.Accs2TxOutputs(et.accOut)
et.acc2State(et.accIn) 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") 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") assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to new account")
//test the map results //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") assert.True(map2ok, "getOrMakeOutputs: account output does not contain new account map item")
} }
@ -137,12 +136,12 @@ func TestValidateInputsBasic(t *testing.T) {
et := newExecTest() et := newExecTest()
//validate input basic //validate input basic
txs := types.Accs2TxInputs(1, et.accIn) inputs := types.Accs2TxInputs(1, et.accIn)
res := validateInputsBasic(txs) res := validateInputsBasic(inputs)
assert.True(res.IsOK(), "validateInputsBasic: expected no error on good tx input. Error: %v", res.Error()) assert.True(res.IsOK(), "validateInputsBasic: expected no error on good tx input. Error: %v", res.Error())
txs[0].Coins[0].Amount = 0 inputs[0].Coins[0].Amount = 0
res = validateInputsBasic(txs) res = validateInputsBasic(inputs)
assert.True(res.IsErr(), "validateInputsBasic: expected error on bad tx input") assert.True(res.IsErr(), "validateInputsBasic: expected error on bad tx input")
} }
@ -157,28 +156,28 @@ func TestValidateInputsAdvanced(t *testing.T) {
accIn3 := types.MakeAcc("fooz") accIn3 := types.MakeAcc("fooz")
//validate inputs advanced //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) 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()) 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 //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") assert.True(res.IsErr(), "validateInputsAdvanced: expected an error on an unsigned tx input")
//test good case sgined //test good case sgined
et.signTx(txs, accIn1, accIn2, accIn3, et.accOut) et.signTx(tx, accIn1, accIn2, accIn3, et.accOut)
totalCoins, res = validateInputsAdvanced(accMap, signBytes, txs.Inputs) totalCoins, res = validateInputsAdvanced(accMap, signBytes, tx.Inputs)
assert.True(res.IsOK(), "validateInputsAdvanced: expected no error on good tx input. Error: %v", res.Error()) assert.True(res.IsOK(), "validateInputsAdvanced: expected no error on good tx input. Error: %v", res.Error())
txsTotalCoins := txs.Inputs[0].Coins. txTotalCoins := tx.Inputs[0].Coins.
Plus(txs.Inputs[1].Coins). Plus(tx.Inputs[1].Coins).
Plus(txs.Inputs[2].Coins) Plus(tx.Inputs[2].Coins)
assert.True(totalCoins.IsEqual(txsTotalCoins), assert.True(totalCoins.IsEqual(txTotalCoins),
"ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txsTotalCoins, totalCoins) "ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txTotalCoins, totalCoins)
} }
func TestValidateInputAdvanced(t *testing.T) { func TestValidateInputAdvanced(t *testing.T) {
@ -186,31 +185,31 @@ func TestValidateInputAdvanced(t *testing.T) {
et := newExecTest() et := newExecTest()
//validate input advanced //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) et.acc2State(et.accIn, et.accOut)
signBytes := txs.SignBytes(et.chainID) signBytes := tx.SignBytes(et.chainID)
//unsigned case //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") assert.True(res.IsErr(), "validateInputAdvanced: expected error on tx input without signature")
//good signed case //good signed case
et.signTx(txs, et.accIn, et.accOut) et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) 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()) assert.True(res.IsOK(), "validateInputAdvanced: expected no error on good tx input. Error: %v", res.Error())
//bad sequence case //bad sequence case
et.accIn.Sequence = 1 et.accIn.Sequence = 1
et.signTx(txs, et.accIn, et.accOut) et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) 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") assert.Equal(abci.CodeType_BaseInvalidSequence, res.Code, "validateInputAdvanced: expected error on tx input with bad sequence")
et.accIn.Sequence = 0 //restore sequence et.accIn.Sequence = 0 //restore sequence
//bad balance case //bad balance case
et.accIn.Balance = types.Coins{{"mycoin", 2}} et.accIn.Balance = types.Coins{{"mycoin", 2}}
et.signTx(txs, et.accIn, et.accOut) et.signTx(tx, et.accIn, et.accOut)
res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0])
assert.Equal(abci.CodeType_BaseInsufficientFunds, res.Code, assert.Equal(abci.CodeType_BaseInsufficientFunds, res.Code,
"validateInputAdvanced: expected error on tx input with insufficient funds %v", et.accIn.Sequence) "validateInputAdvanced: expected error on tx input with insufficient funds %v", et.accIn.Sequence)
} }
@ -220,12 +219,12 @@ func TestValidateOutputsAdvanced(t *testing.T) {
et := newExecTest() et := newExecTest()
//validateOutputsBasic //validateOutputsBasic
txs := types.Accs2TxOutputs(et.accIn) tx := types.Accs2TxOutputs(et.accIn)
res := validateOutputsBasic(txs) res := validateOutputsBasic(tx)
assert.True(res.IsOK(), "validateOutputsBasic: expected no error on good tx output. Error: %v", res.Error()) assert.True(res.IsOK(), "validateOutputsBasic: expected no error on good tx output. Error: %v", res.Error())
txs[0].Coins[0].Amount = 0 tx[0].Coins[0].Amount = 0
res = validateOutputsBasic(txs) res = validateOutputsBasic(tx)
assert.True(res.IsErr(), "validateInputBasic: expected error on bad tx output. Error: %v", res.Error()) 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() et := newExecTest()
//SumOutput //SumOutput
txs := types.Accs2TxOutputs(et.accIn, et.accOut) tx := types.Accs2TxOutputs(et.accIn, et.accOut)
total := sumOutputs(txs) total := sumOutputs(tx)
assert.True(total.IsEqual(txs[0].Coins.Plus(txs[1].Coins)), "sumOutputs: total coins are not equal") assert.True(total.IsEqual(tx[0].Coins.Plus(tx[1].Coins)), "sumOutputs: total coins are not equal")
} }
func TestAdjustBy(t *testing.T) { 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) assert := assert.New(t)
et := newExecTest() et := newExecTest()
//ExecTx //ExecTx
txs := et.getTx(1, et.accOut, et.accIn) tx := types.MakeSendTx(1, et.accOut, et.accIn)
et.acc2State(et.accIn) et.acc2State(et.accIn)
et.acc2State(et.accOut) et.acc2State(et.accOut)
et.signTx(txs, et.accIn) et.signTx(tx, et.accIn)
//Bad Balance //Bad Balance
et.accIn.Balance = types.Coins{{"mycoin", 2}} et.accIn.Balance = types.Coins{{"mycoin", 2}}
et.acc2State(et.accIn) 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) 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.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res)
assert.False(balIn.IsEqual(balInExp), assert.False(balIn.IsEqual(balInExp),
"ExecTx/Bad DeliverTx: balance shouldn't be equal for accIn: got %v, expected: %v", balIn, 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.reset()
et.acc2State(et.accIn) et.acc2State(et.accIn)
et.acc2State(et.accOut) 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) assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res)
//Regular DeliverTx //Regular DeliverTx
et.reset() et.reset()
et.acc2State(et.accIn) et.acc2State(et.accIn)
et.acc2State(et.accOut) 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(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res)
assert.True(balIn.IsEqual(balInExp), assert.True(balIn.IsEqual(balInExp),
"ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp) "ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp)
assert.True(balOut.IsEqual(balOutExp), assert.True(balOut.IsEqual(balOutExp),
"ExecTx/good DeliverTx: unexpected change in output balance, got: %v, expected: %v", balOut, 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 ( import (
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
"github.com/tendermint/basecoin/types" "github.com/tendermint/basecoin/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-wire"
eyes "github.com/tendermint/merkleeyes/client" eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
) )
// CONTRACT: State should be quick to copy. // CONTRACT: State should be quick to copy.
@ -15,6 +14,7 @@ type State struct {
store types.KVStore store types.KVStore
readCache map[string][]byte // optional, for caching writes to store readCache map[string][]byte // optional, for caching writes to store
writeCache *types.KVCache // optional, for caching writes w/o writing to store writeCache *types.KVCache // optional, for caching writes w/o writing to store
logger log.Logger
} }
func NewState(store types.KVStore) *State { func NewState(store types.KVStore) *State {
@ -23,9 +23,14 @@ func NewState(store types.KVStore) *State {
store: store, store: store,
readCache: make(map[string][]byte), readCache: make(map[string][]byte),
writeCache: nil, writeCache: nil,
logger: log.NewNopLogger(),
} }
} }
func (s *State) SetLogger(l log.Logger) {
s.logger = l
}
func (s *State) SetChainID(chainID string) { func (s *State) SetChainID(chainID string) {
s.chainID = chainID s.chainID = chainID
s.store.Set([]byte("base/chain_id"), []byte(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 { 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) { func (s *State) SetAccount(addr []byte, acc *types.Account) {
SetAccount(s, addr, acc) types.SetAccount(s, addr, acc)
} }
func (s *State) CacheWrap() *State { func (s *State) CacheWrap() *State {
@ -71,6 +76,7 @@ func (s *State) CacheWrap() *State {
store: cache, store: cache,
readCache: nil, readCache: nil,
writeCache: cache, 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" "github.com/tendermint/basecoin/types"
eyes "github.com/tendermint/merkleeyes/client" eyes "github.com/tendermint/merkleeyes/client"
"github.com/tendermint/tmlibs/log"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -16,6 +17,7 @@ func TestState(t *testing.T) {
//States and Stores for tests //States and Stores for tests
store := types.NewMemKVStore() store := types.NewMemKVStore()
state := NewState(store) state := NewState(store)
state.SetLogger(log.TestingLogger())
cache := state.CacheWrap() cache := state.CacheWrap()
eyesCli := eyes.NewLocalClient("", 0) eyesCli := eyes.NewLocalClient("", 0)
@ -29,12 +31,14 @@ func TestState(t *testing.T) {
reset := func() { reset := func() {
store = types.NewMemKVStore() store = types.NewMemKVStore()
state = NewState(store) state = NewState(store)
state.SetLogger(log.TestingLogger())
cache = state.CacheWrap() cache = state.CacheWrap()
} }
//set the state to using the eyesCli instead of MemKVStore //set the state to using the eyesCli instead of MemKVStore
useEyesCli := func() { useEyesCli := func() {
state = NewState(eyesCli) state = NewState(eyesCli)
state.SetLogger(log.TestingLogger())
cache = state.CacheWrap() cache = state.CacheWrap()
} }

View File

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

View File

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

View File

@ -4,10 +4,11 @@ import (
"fmt" "fmt"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
"github.com/tendermint/go-wire"
) )
type Account struct { type Account struct {
PubKey crypto.PubKeyS `json:"pub_key"` // May be nil, if not known. PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known.
Sequence int `json:"sequence"` Sequence int `json:"sequence"`
Balance Coins `json:"coins"` Balance Coins `json:"coins"`
} }
@ -31,7 +32,7 @@ func (acc *Account) String() string {
//---------------------------------------- //----------------------------------------
type PrivAccount struct { type PrivAccount struct {
crypto.PrivKeyS crypto.PrivKey
Account Account
} }
@ -49,3 +50,26 @@ type AccountGetterSetter interface {
GetAccount(addr []byte) *Account GetAccount(addr []byte) *Account
SetAccount(addr []byte, acc *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 ( import (
"fmt" "fmt"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
type Coin struct { type Coin struct {
@ -17,22 +20,26 @@ func (coin Coin) String() string {
} }
//regex codes for extracting coins from string //regex codes for extracting coins from string
var reDenom = regexp.MustCompile("([^\\d\\W]+)") var reDenom = regexp.MustCompile("")
var reAmt = regexp.MustCompile("(\\d+)") 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 var coin Coin
if len(str) > 0 { matches := reCoin.FindStringSubmatch(strings.TrimSpace(str))
amt, err := strconv.Atoi(reAmt.FindString(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 { if err != nil {
return coin, err return coin, err
} }
denom := reDenom.FindString(str)
coin = Coin{denom, int64(amt)}
}
coin = Coin{matches[2], int64(amt)}
return coin, nil return coin, nil
} }
@ -53,18 +60,26 @@ func (coins Coins) String() string {
} }
func ParseCoins(str string) (Coins, error) { func ParseCoins(str string) (Coins, error) {
// empty string is empty list...
if len(str) == 0 {
return nil, nil
}
split := strings.Split(str, ",") split := strings.Split(str, ",")
var coins []Coin var coins Coins
for _, el := range split { for _, el := range split {
if len(el) > 0 {
coin, err := ParseCoin(el) coin, err := ParseCoin(el)
if err != nil { if err != nil {
return coins, err return coins, err
} }
coins = append(coins, coin) 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 return coins, nil
@ -195,3 +210,10 @@ func (coins Coins) IsNonnegative() bool {
} }
return true 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" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
) )
func TestCoins(t *testing.T) { func TestCoins(t *testing.T) {
@ -56,30 +55,85 @@ func TestCoins(t *testing.T) {
//Test the parse coin and parse coins functionality //Test the parse coin and parse coins functionality
func TestParse(t *testing.T) { func TestParse(t *testing.T) {
assert, require := assert.New(t), require.New(t) assert := assert.New(t)
makeCoin := func(str string) Coin { cases := []struct {
coin, err := ParseCoin(str) input string
require.Nil(err) valid bool // if false, we expect an error on parse
return coin 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 { for _, tc := range cases {
coin, err := ParseCoins(str) res, err := ParseCoins(tc.input)
require.Nil(err) if !tc.valid {
return coin 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") func TestSortCoins(t *testing.T) {
assert.Equal(Coin{"barCoin", 10}, makeCoin("10 barCoin"), "ParseCoin makes bad coins") assert := assert.New(t)
//testing ParseCoins Function good := Coins{
assert.True(Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")), Coin{"GAS", 1},
"ParseCoins doesn't parse a single coin") Coin{"MINERAL", 1},
assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")), Coin{"TREE", 1},
"ParseCoins doesn't properly parse two coins") }
assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")), empty := Coins{
"ParseCoins doesn't properly parse two coins which use spaces") 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" "container/list"
"fmt" "fmt"
. "github.com/tendermint/go-common" . "github.com/tendermint/tmlibs/common"
) )
type KVStore interface { type KVStore interface {

View File

@ -3,18 +3,19 @@ package types
// Helper functions for testing // Helper functions for testing
import ( import (
cmn "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
cmn "github.com/tendermint/tmlibs/common"
) )
// Creates a PrivAccount from secret. // Creates a PrivAccount from secret.
// The amount is not set. // The amount is not set.
func PrivAccountFromSecret(secret string) PrivAccount { func PrivAccountFromSecret(secret string) PrivAccount {
privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret)) privKey :=
crypto.GenPrivKeyEd25519FromSecret([]byte(secret)).Wrap()
privAccount := PrivAccount{ privAccount := PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey}, PrivKey: privKey,
Account: Account{ Account: Account{
PubKey: crypto.PubKeyS{privKey.PubKey()}, PubKey: privKey.PubKey(),
}, },
} }
return privAccount return privAccount
@ -30,10 +31,10 @@ func RandAccounts(num int, minAmount int64, maxAmount int64) []PrivAccount {
balance += cmn.RandInt64() % (maxAmount - minAmount) balance += cmn.RandInt64() % (maxAmount - minAmount)
} }
privKey := crypto.GenPrivKeyEd25519() privKey := crypto.GenPrivKeyEd25519().Wrap()
pubKey := crypto.PubKeyS{privKey.PubKey()} pubKey := privKey.PubKey()
privAccs[i] = PrivAccount{ privAccs[i] = PrivAccount{
PrivKeyS: crypto.PrivKeyS{privKey}, PrivKey: privKey,
Account: Account{ Account: Account{
PubKey: pubKey, PubKey: pubKey,
Balance: Coins{Coin{"", balance}}, Balance: Coins{Coin{"", balance}},
@ -85,20 +86,20 @@ func Accs2TxOutputs(accs ...PrivAccount) []TxOutput {
return txs return txs
} }
func GetTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx { func MakeSendTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx {
txs := &SendTx{ tx := &SendTx{
Gas: 0, Gas: 0,
Fee: Coin{"mycoin", 1}, Fee: Coin{"mycoin", 1},
Inputs: Accs2TxInputs(seq, accsIn...), Inputs: Accs2TxInputs(seq, accsIn...),
Outputs: Accs2TxOutputs(accOut), Outputs: Accs2TxOutputs(accOut),
} }
return txs return tx
} }
func SignTx(chainID string, tx *SendTx, accs ...PrivAccount) { func SignTx(chainID string, tx *SendTx, accs ...PrivAccount) {
signBytes := tx.SignBytes(chainID) signBytes := tx.SignBytes(chainID)
for i, _ := range tx.Inputs { 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" "encoding/json"
abci "github.com/tendermint/abci/types" abci "github.com/tendermint/abci/types"
. "github.com/tendermint/go-common"
"github.com/tendermint/go-crypto" "github.com/tendermint/go-crypto"
"github.com/tendermint/go-data"
"github.com/tendermint/go-wire" "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 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() { func init() {
txMapper = data.NewMapper(TxS{}). txMapper = data.NewMapper(TxS{}).
RegisterImplementation(&SendTx{}, TxNameSend, TxTypeSend). RegisterImplementation(&SendTx{}, TxNameSend, TxTypeSend).
@ -46,7 +46,7 @@ func init() {
// TxS add json serialization to Tx // TxS add json serialization to Tx
type TxS struct { type TxS struct {
Tx Tx `json:"unwrap"`
} }
func (p TxS) MarshalJSON() ([]byte, error) { func (p TxS) MarshalJSON() ([]byte, error) {
@ -67,8 +67,8 @@ type TxInput struct {
Address data.Bytes `json:"address"` // Hash of the PubKey Address data.Bytes `json:"address"` // Hash of the PubKey
Coins Coins `json:"coins"` // Coins Coins `json:"coins"` //
Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput 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 Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx
PubKey crypto.PubKeyS `json:"pub_key"` // Is present iff Sequence == 0 PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0
} }
func (txIn TxInput) ValidateBasic() abci.Result { func (txIn TxInput) ValidateBasic() abci.Result {
@ -104,13 +104,7 @@ func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput {
Sequence: sequence, Sequence: sequence,
} }
if sequence == 1 { if sequence == 1 {
// safely wrap if needed input.PubKey = pubKey
// TODO: extract this as utility function?
ps, ok := pubKey.(crypto.PubKeyS)
if !ok {
ps = crypto.PubKeyS{pubKey}
}
input.PubKey = ps
} }
return input return input
} }
@ -122,10 +116,33 @@ type TxOutput struct {
Coins Coins `json:"coins"` // Coins Coins `json:"coins"` //
} }
func (txOut TxOutput) ValidateBasic() abci.Result { // An output destined for another chain may be formatted as `chainID/address`.
if len(txOut.Address) != 20 { // ChainAndAddress returns the chainID prefix and the address.
return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length") // 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() { if !txOut.Coins.IsValid() {
return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins)) return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins))
} }
@ -151,25 +168,21 @@ type SendTx struct {
func (tx *SendTx) SignBytes(chainID string) []byte { func (tx *SendTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID) signBytes := wire.BinaryBytes(chainID)
sigz := make([]crypto.Signature, len(tx.Inputs)) sigz := make([]crypto.Signature, len(tx.Inputs))
for i, input := range tx.Inputs { for i := range tx.Inputs {
sigz[i] = input.Signature.Signature sigz[i] = tx.Inputs[i].Signature
tx.Inputs[i].Signature.Signature = nil tx.Inputs[i].Signature = crypto.Signature{}
} }
signBytes = append(signBytes, wire.BinaryBytes(tx)...) signBytes = append(signBytes, wire.BinaryBytes(tx)...)
for i := range tx.Inputs { for i := range tx.Inputs {
tx.Inputs[i].Signature.Signature = sigz[i] tx.Inputs[i].Signature = sigz[i]
} }
return signBytes return signBytes
} }
func (tx *SendTx) SetSignature(addr []byte, sig crypto.Signature) bool { 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 { for i, input := range tx.Inputs {
if bytes.Equal(input.Address, addr) { if bytes.Equal(input.Address, addr) {
tx.Inputs[i].Signature = sigs tx.Inputs[i].Signature = sig
return true return true
} }
} }
@ -193,18 +206,14 @@ type AppTx struct {
func (tx *AppTx) SignBytes(chainID string) []byte { func (tx *AppTx) SignBytes(chainID string) []byte {
signBytes := wire.BinaryBytes(chainID) signBytes := wire.BinaryBytes(chainID)
sig := tx.Input.Signature sig := tx.Input.Signature
tx.Input.Signature.Signature = nil tx.Input.Signature = crypto.Signature{}
signBytes = append(signBytes, wire.BinaryBytes(tx)...) signBytes = append(signBytes, wire.BinaryBytes(tx)...)
tx.Input.Signature = sig tx.Input.Signature = sig
return signBytes return signBytes
} }
func (tx *AppTx) SetSignature(sig crypto.Signature) bool { func (tx *AppTx) SetSignature(sig crypto.Signature) bool {
sigs, ok := sig.(crypto.SignatureS) tx.Input.Signature = sig
if !ok {
sigs = crypto.SignatureS{sig}
}
tx.Input.Signature = sigs
return true return true
} }

View File

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

View File

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