diff --git a/CHANGELOG.md b/CHANGELOG.md index 035d464cf..98aaa0428 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,30 @@ # Changelog +## 0.5.0 (May 27, 2017) + +BREAKING CHANGES: +- only those related to the tendermint 0.9 -> 0.10 upgrade + +ENHANCEMENTS: +- basecoin cli + - integrates tendermint 0.10.0 and unifies cli (init, unsafe_reset_all, ...) + - integrate viper, all command line flags can also be defined in environmental variables or config.toml +- genesis file + - you can define accounts with either address or pub_key + - sorts coins for you, so no silent errors if not in alphabetical order +- [light-client](https://github.com/tendermint/light-client) integration + - no longer must you trust the node you connect to, prove everything! + - new [basecli command](./cmd/basecli/README.md) + - integrated [key management](https://github.com/tendermint/go-crypto/blob/master/cmd/README.md), stored encrypted locally + - tracks validator set changes and proves everything from one initial validator seed + - `basecli proof state` gets complete proofs for any abci state + - `basecli proof tx` gets complete proof where a tx was stored in the chain + - `basecli proxy` exposes tendermint rpc, but only passes through results after doing complete verification + +BUG FIXES: +- no more silently ignored error with invalid coin names (eg. "17.22foo coin" used to parse as "17 foo", not warning/error) + + ## 0.4.1 (April 26, 2017) BUG FIXES: @@ -74,7 +99,7 @@ We also changed `chainID` to `chain_id` and consolidated to have just one of the FEATURES: -- Introduce `basecoin init` and `basecoin unsafe_reset_all` +- Introduce `basecoin init` and `basecoin unsafe_reset_all` ## 0.2.0 (March 6, 2017) @@ -86,8 +111,8 @@ BREAKING CHANGES: FEATURES: -- CLI for sending transactions and querying the state, -designed to be easily extensible as plugins are implemented +- CLI for sending transactions and querying the state, +designed to be easily extensible as plugins are implemented - Run Basecoin in-process with Tendermint - Add `/account` path in Query - IBC plugin for InterBlockchain Communication diff --git a/app/app.go b/app/app.go index fe1773669..27a3b9c98 100644 --- a/app/app.go +++ b/app/app.go @@ -1,13 +1,15 @@ package app import ( + "encoding/hex" "encoding/json" "strings" abci "github.com/tendermint/abci/types" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-wire" + wire "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" + . "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" sm "github.com/tendermint/basecoin/state" "github.com/tendermint/basecoin/types" @@ -24,6 +26,7 @@ type Basecoin struct { state *sm.State cacheState *sm.State plugins *types.Plugins + logger log.Logger } func NewBasecoin(eyesCli *eyes.Client) *Basecoin { @@ -34,9 +37,15 @@ func NewBasecoin(eyesCli *eyes.Client) *Basecoin { state: state, cacheState: nil, plugins: plugins, + logger: log.NewNopLogger(), } } +func (app *Basecoin) SetLogger(l log.Logger) { + app.logger = l + app.state.SetLogger(l.With("module", "state")) +} + // XXX For testing, not thread safe! func (app *Basecoin) GetState() *sm.State { return app.state.CacheWrap() @@ -60,7 +69,7 @@ func (app *Basecoin) SetOption(key string, value string) string { if plugin == nil { return "Invalid plugin name: " + pluginName } - log.Notice("SetOption on plugin", "plugin", pluginName, "key", key, "value", value) + app.logger.Info("SetOption on plugin", "plugin", pluginName, "key", key, "value", value) return plugin.SetOption(app.state, key, value) } else { // Set option on basecoin @@ -69,13 +78,18 @@ func (app *Basecoin) SetOption(key string, value string) string { app.state.SetChainID(value) return "Success" case "account": - var acc types.Account + var acc GenesisAccount err := json.Unmarshal([]byte(value), &acc) if err != nil { return "Error decoding acc message: " + err.Error() } - app.state.SetAccount(acc.PubKey.Address(), &acc) - log.Notice("SetAccount", "addr", acc.PubKey.Address(), "acc", acc) + acc.Balance.Sort() + addr, err := acc.GetAddr() + if err != nil { + return "Invalid address: " + err.Error() + } + app.state.SetAccount(addr, acc.ToAccount()) + app.logger.Info("SetAccount", "addr", hex.EncodeToString(addr), "acc", acc) return "Success" } @@ -136,7 +150,7 @@ func (app *Basecoin) Query(reqQuery abci.RequestQuery) (resQuery abci.ResponseQu // handle special path for account info if reqQuery.Path == "/account" { reqQuery.Path = "/key" - reqQuery.Data = sm.AccountKey(reqQuery.Data) + reqQuery.Data = types.AccountKey(reqQuery.Data) } resQuery, err := app.eyesCli.QuerySync(reqQuery) diff --git a/app/app_test.go b/app/app_test.go index 011d9d756..1f150e8c4 100644 --- a/app/app_test.go +++ b/app/app_test.go @@ -1,8 +1,8 @@ package app import ( + "encoding/hex" "encoding/json" - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -10,8 +10,9 @@ import ( abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/types" - "github.com/tendermint/go-wire" + wire "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" + "github.com/tendermint/tmlibs/log" ) //-------------------------------------------------------- @@ -36,7 +37,7 @@ func newAppTest(t *testing.T) *appTest { // make a tx sending 5mycoin from each accIn to accOut func (at *appTest) getTx(seq int) *types.SendTx { - tx := types.GetTx(seq, at.accOut, at.accIn) + tx := types.MakeSendTx(seq, at.accOut, at.accIn) types.SignTx(at.chainID, tx, at.accIn) return tx } @@ -56,6 +57,7 @@ func (at *appTest) reset() { eyesCli := eyes.NewLocalClient("", 0) at.app = NewBasecoin(eyesCli) + at.app.SetLogger(log.TestingLogger().With("module", "app")) res := at.app.SetOption("base/chain_id", at.chainID) require.EqualValues(at.t, res, "Success") @@ -101,9 +103,11 @@ func TestSplitKey(t *testing.T) { func TestSetOption(t *testing.T) { assert := assert.New(t) + require := require.New(t) eyesCli := eyes.NewLocalClient("", 0) app := NewBasecoin(eyesCli) + app.SetLogger(log.TestingLogger().With("module", "app")) //testing ChainID chainID := "testChain" @@ -111,11 +115,43 @@ func TestSetOption(t *testing.T) { assert.EqualValues(app.GetState().GetChainID(), chainID) assert.EqualValues(res, "Success") + // make a nice account... accIn := types.MakeAcc("input0") accsInBytes, err := json.Marshal(accIn.Account) assert.Nil(err) res = app.SetOption("base/account", string(accsInBytes)) - assert.EqualValues(res, "Success") + require.EqualValues(res, "Success") + // make sure it is set correctly, with some balance + acct := types.GetAccount(app.GetState(), accIn.PubKey.Address()) + require.NotNil(acct) + assert.Equal(accIn.Balance, acct.Balance) + + // let's parse an account with badly sorted coins... + unsortAddr, err := hex.DecodeString("C471FB670E44D219EE6DF2FC284BE38793ACBCE1") + require.Nil(err) + unsortCoins := types.Coins{{"BTC", 789}, {"eth", 123}} + unsortAcc := `{ + "pub_key": { + "type": "ed25519", + "data": "AD084F0572C116D618B36F2EB08240D1BAB4B51716CCE0E7734B89C8936DCE9A" + }, + "coins": [ + { + "denom": "eth", + "amount": 123 + }, + { + "denom": "BTC", + "amount": 789 + } + ] +}` + res = app.SetOption("base/account", unsortAcc) + require.EqualValues(res, "Success") + acct = types.GetAccount(app.GetState(), unsortAddr) + require.NotNil(acct) + assert.True(acct.Balance.IsValid()) + assert.Equal(unsortCoins, acct.Balance) res = app.SetOption("base/dslfkgjdas", "") assert.NotEqual(res, "Success") @@ -125,6 +161,7 @@ func TestSetOption(t *testing.T) { res = app.SetOption("dslfkgjdas/szfdjzs", "") assert.NotEqual(res, "Success") + } // Test CheckTx and DeliverTx with insufficient and sufficient balance @@ -176,7 +213,5 @@ func TestQuery(t *testing.T) { Path: "/account", Data: at.accIn.Account.PubKey.Address(), }) - fmt.Println(resQueryPreCommit) - fmt.Println(resQueryPostCommit) assert.NotEqual(resQueryPreCommit, resQueryPostCommit, "Query should change before/after commit") } diff --git a/app/genesis.go b/app/genesis.go index ad8b2b6c9..fa6a5ac30 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -1,12 +1,15 @@ package app import ( + "bytes" "encoding/json" "github.com/pkg/errors" + "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - //tmtypes "github.com/tendermint/tendermint/types" + crypto "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire/data" + cmn "github.com/tendermint/tmlibs/common" ) func (app *Basecoin) LoadGenesis(path string) error { @@ -26,13 +29,13 @@ func (app *Basecoin) LoadGenesis(path string) error { } r := app.SetOption("base/account", string(accBytes)) // TODO: SetOption returns an error - log.Notice("Done setting Account via SetOption", "result", r) + app.logger.Info("Done setting Account via SetOption", "result", r) } // set plugin options for _, kv := range genDoc.AppOptions.pluginOptions { r := app.SetOption(kv.Key, kv.Value) - log.Notice("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value) + app.logger.Info("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value) } return nil } @@ -49,7 +52,7 @@ type FullGenesisDoc struct { } type GenesisDoc struct { - Accounts []types.Account `json:"accounts"` + Accounts []GenesisAccount `json:"accounts"` PluginOptions []json.RawMessage `json:"plugin_options"` pluginOptions []keyValue // unmarshaled rawmessages @@ -61,11 +64,7 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) { return nil, errors.Wrap(err, "loading genesis file") } - // the tendermint genesis is go-wire - // tmGenesis := new(tmtypes.GenesisDoc) - // err = wire.ReadJSONBytes(bytes, tmGenesis) - - // the basecoin genesis go-data :) + // the basecoin genesis go-wire/data :) genDoc := new(FullGenesisDoc) err = json.Unmarshal(bytes, genDoc) if err != nil { @@ -102,3 +101,40 @@ func parseGenesisList(kvz_ []json.RawMessage) (kvz []keyValue, err error) { } return kvz, nil } + +/**** code to parse accounts from genesis docs ***/ + +type GenesisAccount struct { + Address data.Bytes `json:"address"` + // this from types.Account (don't know how to embed this properly) + PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. + Sequence int `json:"sequence"` + Balance types.Coins `json:"coins"` +} + +func (g GenesisAccount) ToAccount() *types.Account { + return &types.Account{ + PubKey: g.PubKey, + Sequence: g.Sequence, + Balance: g.Balance, + } +} + +func (g GenesisAccount) GetAddr() ([]byte, error) { + noAddr, noPk := len(g.Address) == 0, g.PubKey.Empty() + + if noAddr { + if noPk { + return nil, errors.New("No address given") + } + return g.PubKey.Address(), nil + } + if noPk { // but is addr... + return g.Address, nil + } + // now, we have both, make sure they check out + if bytes.Equal(g.Address, g.PubKey.Address()) { + return g.Address, nil + } + return nil, errors.New("Address and pubkey don't match") +} diff --git a/app/genesis_test.go b/app/genesis_test.go index 74ca22e61..b2a590bcf 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -7,12 +7,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - cmn "github.com/tendermint/go-common" + "github.com/tendermint/basecoin/types" "github.com/tendermint/go-crypto" eyescli "github.com/tendermint/merkleeyes/client" + cmn "github.com/tendermint/tmlibs/common" ) const genesisFilepath = "./testdata/genesis.json" +const genesisAcctFilepath = "./testdata/genesis2.json" func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) @@ -34,11 +36,15 @@ func TestLoadGenesis(t *testing.T) { // make sure balance is proper assert.Equal(2, len(acct.Balance)) - assert.EqualValues(12345, acct.Balance[0].Amount) - assert.EqualValues("blank", acct.Balance[0].Denom) + assert.True(acct.Balance.IsValid()) + // note, that we now sort them to be valid + assert.EqualValues(654321, acct.Balance[0].Amount) + assert.EqualValues("ETH", acct.Balance[0].Denom) + assert.EqualValues(12345, acct.Balance[1].Amount) + assert.EqualValues("blank", acct.Balance[1].Denom) // and public key is parsed properly - apk := acct.PubKey.PubKey + apk := acct.PubKey.Unwrap() require.NotNil(apk) epk, ok := apk.(crypto.PubKeyEd25519) if assert.True(ok) { @@ -46,13 +52,58 @@ func TestLoadGenesis(t *testing.T) { } } +// Fix for issue #89, change the parse format for accounts in genesis.json +func TestLoadGenesisAccountAddress(t *testing.T) { + assert, require := assert.New(t), require.New(t) + + eyesCli := eyescli.NewLocalClient("", 0) + app := NewBasecoin(eyesCli) + err := app.LoadGenesis(genesisAcctFilepath) + require.Nil(err, "%+v", err) + + // check the chain id + assert.Equal("addr_accounts_chain", app.GetState().GetChainID()) + + // make sure the accounts were set properly + cases := []struct { + addr string + exists bool + hasPubkey bool + coins types.Coins + }{ + // this comes from a public key, should be stored proper (alice) + {"62035D628DE7543332544AA60D90D3693B6AD51B", true, true, types.Coins{{"one", 111}}}, + // this comes from an address, should be stored proper (bob) + {"C471FB670E44D219EE6DF2FC284BE38793ACBCE1", true, false, types.Coins{{"two", 222}}}, + // this one had a mismatched address and pubkey, should not store under either (carl) + {"1234ABCDD18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is given addr + {"700BEC5ED18E8EFE3FFC4B0506BF9BF8E5B0D9E9", false, false, nil}, // this is addr of the given pubkey + // this comes from a secp256k1 public key, should be stored proper (sam) + {"979F080B1DD046C452C2A8A250D18646C6B669D4", true, true, types.Coins{{"four", 444}}}, + } + + for _, tc := range cases { + addr, err := hex.DecodeString(tc.addr) + require.Nil(err, tc.addr) + acct := app.GetState().GetAccount(addr) + if !tc.exists { + assert.Nil(acct, tc.addr) + } else if assert.NotNil(acct, tc.addr) { + // it should and does exist... + assert.True(acct.Balance.IsValid()) + assert.Equal(tc.coins, acct.Balance) + assert.Equal(!tc.hasPubkey, acct.PubKey.Empty(), tc.addr) + } + } +} + func TestParseGenesisList(t *testing.T) { assert, require := assert.New(t), require.New(t) bytes, err := cmn.ReadFile(genesisFilepath) require.Nil(err, "loading genesis file %+v", err) - // the basecoin genesis go-data :) + // the basecoin genesis go-wire/data :) genDoc := new(FullGenesisDoc) err = json.Unmarshal(bytes, genDoc) require.Nil(err, "unmarshaling genesis file %+v", err) diff --git a/app/log.go b/app/log.go deleted file mode 100644 index 52dc2ddfa..000000000 --- a/app/log.go +++ /dev/null @@ -1,7 +0,0 @@ -package app - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "app") diff --git a/app/testdata/genesis2.json b/app/testdata/genesis2.json new file mode 100644 index 000000000..a880b3c64 --- /dev/null +++ b/app/testdata/genesis2.json @@ -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 + } + ] + }] + } +} diff --git a/circle.yml b/circle.yml index f24f3ab38..3e8710ca9 100644 --- a/circle.yml +++ b/circle.yml @@ -1,26 +1,26 @@ machine: environment: - GOPATH: /home/ubuntu/.go_workspace - REPO: $GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME + GOPATH: "$HOME/.go_workspace" + PROJECT_PARENT_PATH: "$GOPATH/src/github.com/$CIRCLE_PROJECT_USERNAME" + REPO: "$PROJECT_PARENT_PATH/$CIRCLE_PROJECT_REPONAME" + PATH: "$GOPATH/bin:$PATH" hosts: circlehost: 127.0.0.1 localhost: 127.0.0.1 -checkout: - post: - - rm -rf $REPO - - mkdir -p $HOME/.go_workspace/src/github.com/$CIRCLE_PROJECT_USERNAME - - mv $HOME/$CIRCLE_PROJECT_REPONAME $REPO - dependencies: override: - go get github.com/Masterminds/glide - go version - glide --version - - "cd $REPO && glide install && go install ./cmd/basecoin" + - mkdir -p "$PROJECT_PARENT_PATH" + - ln -sf "$HOME/$CIRCLE_PROJECT_REPONAME/" "$REPO" + - env test: override: + - "cd $REPO && glide install && go install ./cmd/basecoin" + - ls $GOPATH/bin - "cd $REPO && make test" - "cd $REPO/demo && bash start.sh" diff --git a/cmd/basecli/LIGHT_NODE.md b/cmd/basecli/LIGHT_NODE.md new file mode 100644 index 000000000..bb58ebb19 --- /dev/null +++ b/cmd/basecli/LIGHT_NODE.md @@ -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://: --chainid +``` + +## 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. diff --git a/cmd/basecli/README.md b/cmd/basecli/README.md new file mode 100644 index 000000000..efa737512 --- /dev/null +++ b/cmd/basecli/README.md @@ -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 +``` + diff --git a/cmd/basecli/commands/adapters.go b/cmd/basecli/commands/adapters.go new file mode 100644 index 000000000..516cb2f0d --- /dev/null +++ b/cmd/basecli/commands/adapters.go @@ -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 ,...") + fs.String("fee", "", "Coins for the transaction fee of the format ") + 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 ,...") + fs.String("fee", "", "Coins for the transaction fee of the format ") + 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 +} diff --git a/cmd/basecli/commands/apptx.go b/cmd/basecli/commands/apptx.go new file mode 100644 index 000000000..7cf9e3c6a --- /dev/null +++ b/cmd/basecli/commands/apptx.go @@ -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 +} diff --git a/cmd/basecli/commands/sendtx.go b/cmd/basecli/commands/sendtx.go new file mode 100644 index 000000000..0660ed22c --- /dev/null +++ b/cmd/basecli/commands/sendtx.go @@ -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 +} diff --git a/cmd/basecli/counter/counter.go b/cmd/basecli/counter/counter.go new file mode 100644 index 000000000..3576e1629 --- /dev/null +++ b/cmd/basecli/counter/counter.go @@ -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 ,...") + 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) +} diff --git a/cmd/basecli/main.go b/cmd/basecli/main.go new file mode 100644 index 000000000..01dc64683 --- /dev/null +++ b/cmd/basecli/main.go @@ -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() +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 5b37ab7fe..5bc03e0fa 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -1,9 +1,12 @@ package main import ( + "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/tmlibs/cli" ) func main() { @@ -15,6 +18,7 @@ func main() { RootCmd.AddCommand( commands.InitCmd, commands.StartCmd, + commands.RelayCmd, commands.TxCmd, commands.QueryCmd, commands.KeyCmd, @@ -25,5 +29,8 @@ func main() { commands.VersionCmd, ) - commands.ExecuteWithDebug(RootCmd) + cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin")) + if err := cmd.Execute(); err != nil { + os.Exit(1) + } } diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 8b0938826..4297bba42 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -10,8 +10,8 @@ import ( "github.com/tendermint/basecoin/plugins/ibc" - "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" + "github.com/tendermint/merkleeyes/iavl" tmtypes "github.com/tendermint/tendermint/types" ) @@ -196,13 +196,18 @@ func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error { return err } + var payload ibc.Payload + if err := wire.ReadBinaryBytes(payloadBytes, &payload); err != nil { + return err + } + ibcTx := ibc.IBCPacketCreateTx{ Packet: ibc.Packet{ SrcChainID: fromChain, DstChainID: toChain, Sequence: sequence, Type: packetType, - Payload: payloadBytes, + Payload: payload, }, } @@ -229,7 +234,7 @@ func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error { } var packet ibc.Packet - proof := new(merkle.IAVLProof) + proof := new(iavl.IAVLProof) err = wire.ReadBinaryBytes(packetBytes, &packet) if err != nil { diff --git a/cmd/commands/init.go b/cmd/commands/init.go index f7fae72e6..2cfbbcd94 100644 --- a/cmd/commands/init.go +++ b/cmd/commands/init.go @@ -6,8 +6,6 @@ import ( "path" "github.com/spf13/cobra" - - cmn "github.com/tendermint/go-common" ) //commands @@ -33,15 +31,17 @@ func setupFile(path, data string, perm os.FileMode) (int, error) { } func initCmd(cmd *cobra.Command, args []string) error { - rootDir := BasecoinRoot("") - - cmn.EnsureDir(rootDir, 0777) + // this will ensure that config.toml is there if not yet created, and create dir + cfg, err := getTendermintConfig() + if err != nil { + return err + } // initalize basecoin - genesisFile := path.Join(rootDir, "genesis.json") - privValFile := path.Join(rootDir, "priv_validator.json") - key1File := path.Join(rootDir, "key.json") - key2File := path.Join(rootDir, "key2.json") + genesisFile := cfg.GenesisFile() + privValFile := cfg.PrivValidatorFile() + key1File := path.Join(cfg.RootDir, "key.json") + key2File := path.Join(cfg.RootDir, "key2.json") mod1, err := setupFile(genesisFile, GenesisJSON, 0644) if err != nil { @@ -61,29 +61,29 @@ func initCmd(cmd *cobra.Command, args []string) error { } if (mod1 + mod2 + mod3 + mod4) > 0 { - log.Notice("Initialized Basecoin", "genesis", genesisFile, "key", key1File) + logger.Info("Initialized Basecoin", "genesis", genesisFile, "key", key1File) } else { - log.Notice("Already initialized", "priv_validator", privValFile) + logger.Info("Already initialized", "priv_validator", privValFile) } return nil } var PrivValJSON = `{ - "address": "7A956FADD20D3A5B2375042B2959F8AB172A058F", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "D07ABE82A8B15559A983B2DB5D4842B2B6E4D6AF58B080005662F424F17D68C17B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" - ], - "pub_key": [ - 1, - "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" - ] + "address": "7A956FADD20D3A5B2375042B2959F8AB172A058F", + "last_height": 0, + "last_round": 0, + "last_signature": null, + "last_signbytes": "", + "last_step": 0, + "priv_key": { + "type": "ed25519", + "data": "D07ABE82A8B15559A983B2DB5D4842B2B6E4D6AF58B080005662F424F17D68C17B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" + }, + "pub_key": { + "type": "ed25519", + "data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" + } }` var GenesisJSON = `{ @@ -94,10 +94,10 @@ var GenesisJSON = `{ { "amount": 10, "name": "", - "pub_key": [ - 1, - "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" - ] + "pub_key": { + "type": "ed25519", + "data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" + } } ], "app_options": { @@ -117,25 +117,25 @@ var GenesisJSON = `{ }` var Key1JSON = `{ - "address": "1B1BE55F969F54064628A63B9559E7C21C925165", - "priv_key": { - "type": "ed25519", - "data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" - }, - "pub_key": { - "type": "ed25519", - "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" - } + "address": "1B1BE55F969F54064628A63B9559E7C21C925165", + "priv_key": { + "type": "ed25519", + "data": "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D1619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + }, + "pub_key": { + "type": "ed25519", + "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + } }` var Key2JSON = `{ - "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", - "priv_key": { - "type": "ed25519", - "data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" - }, - "pub_key": { - "type": "ed25519", - "data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" - } + "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", + "priv_key": { + "type": "ed25519", + "data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + }, + "pub_key": { + "type": "ed25519", + "data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + } }` diff --git a/cmd/commands/key.go b/cmd/commands/key.go index 91678f625..4f0b25381 100644 --- a/cmd/commands/key.go +++ b/cmd/commands/key.go @@ -10,8 +10,10 @@ import ( //"github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/go-crypto" + "github.com/tendermint/tmlibs/cli" ) //commands @@ -62,9 +64,9 @@ func (a *Address) UnmarshalJSON(addrHex []byte) error { } type Key struct { - Address Address `json:"address"` - PubKey crypto.PubKeyS `json:"pub_key"` - PrivKey crypto.PrivKeyS `json:"priv_key"` + Address Address `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` } // Implements Signer @@ -75,18 +77,25 @@ func (k *Key) Sign(msg []byte) crypto.Signature { // Generates a new validator with private key. func genKey() *Key { privKey := crypto.GenPrivKeyEd25519() - addrBytes := privKey.PubKey().Address() + pubKey := privKey.PubKey() + addrBytes := pubKey.Address() var addr Address copy(addr[:], addrBytes) return &Key{ Address: addr, - PubKey: crypto.PubKeyS{privKey.PubKey()}, - PrivKey: crypto.PrivKeyS{privKey}, + PubKey: pubKey, + PrivKey: privKey.Wrap(), } } func LoadKey(keyFile string) (*Key, error) { - filePath := path.Join(BasecoinRoot(""), keyFile) + filePath := keyFile + + if !strings.HasPrefix(keyFile, "/") && !strings.HasPrefix(keyFile, ".") { + rootDir := viper.GetString(cli.HomeFlag) + filePath = path.Join(rootDir, keyFile) + } + keyJSONBytes, err := ioutil.ReadFile(filePath) if err != nil { return nil, err diff --git a/cmd/commands/log.go b/cmd/commands/log.go deleted file mode 100644 index 720e168b5..000000000 --- a/cmd/commands/log.go +++ /dev/null @@ -1,7 +0,0 @@ -package commands - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "commands") diff --git a/cmd/commands/query.go b/cmd/commands/query.go index 7d79c2f1f..330ba1514 100644 --- a/cmd/commands/query.go +++ b/cmd/commands/query.go @@ -8,8 +8,9 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" - "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" + "github.com/tendermint/merkleeyes/iavl" + "github.com/tendermint/tendermint/rpc/client" tmtypes "github.com/tendermint/tendermint/types" ) @@ -120,7 +121,8 @@ func accountCmd(cmd *cobra.Command, args []string) error { return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err) } - acc, err := getAcc(nodeFlag, addr) + httpClient := client.NewHTTP(nodeFlag, "/websocket") + acc, err := getAccWithClient(httpClient, addr) if err != nil { return err } @@ -202,7 +204,7 @@ func verifyCmd(cmd *cobra.Command, args []string) error { return errors.Errorf("Proof (%v) is invalid hex: %v\n", proofFlag, err) } - proof, err := merkle.ReadProof(proofBytes) + proof, err := iavl.ReadProof(proofBytes) if err != nil { return errors.Errorf("Error unmarshalling proof: %v\n", err) } diff --git a/cmd/commands/relay.go b/cmd/commands/relay.go new file mode 100644 index 000000000..5a2cacfc9 --- /dev/null +++ b/cmd/commands/relay.go @@ -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 + +} diff --git a/cmd/commands/reset.go b/cmd/commands/reset.go index 7c532fbef..4d38f94bf 100644 --- a/cmd/commands/reset.go +++ b/cmd/commands/reset.go @@ -4,7 +4,6 @@ import ( "github.com/spf13/cobra" tmcmd "github.com/tendermint/tendermint/cmd/tendermint/commands" - tmcfg "github.com/tendermint/tendermint/config/tendermint" ) var UnsafeResetAllCmd = &cobra.Command{ @@ -14,8 +13,10 @@ var UnsafeResetAllCmd = &cobra.Command{ } func unsafeResetAllCmd(cmd *cobra.Command, args []string) error { - basecoinDir := BasecoinRoot("") - tmConfig := tmcfg.GetConfig(basecoinDir) - tmcmd.ResetAll(tmConfig, log) + cfg, err := getTendermintConfig() + if err != nil { + return err + } + tmcmd.ResetAll(cfg.DBDir(), cfg.PrivValidatorFile(), logger) return nil } diff --git a/cmd/commands/root.go b/cmd/commands/root.go new file mode 100644 index 000000000..ff70f1980 --- /dev/null +++ b/cmd/commands/root.go @@ -0,0 +1,11 @@ +package commands + +import ( + "os" + + "github.com/tendermint/tmlibs/log" +) + +var ( + logger = log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "main") +) diff --git a/cmd/commands/start.go b/cmd/commands/start.go index 4578738ce..7a15f59df 100644 --- a/cmd/commands/start.go +++ b/cmd/commands/start.go @@ -7,15 +7,18 @@ import ( "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/abci/server" - cmn "github.com/tendermint/go-common" eyes "github.com/tendermint/merkleeyes/client" + "github.com/tendermint/tmlibs/cli" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" - tmcfg "github.com/tendermint/tendermint/config/tendermint" + "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/proxy" - tmtypes "github.com/tendermint/tendermint/types" + "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" ) @@ -49,12 +52,12 @@ func init() { } func startCmd(cmd *cobra.Command, args []string) error { - basecoinDir := BasecoinRoot("") + rootDir := viper.GetString(cli.HomeFlag) // Connect to MerkleEyes var eyesCli *eyes.Client if eyesFlag == "local" { - eyesCli = eyes.NewLocalClient(path.Join(basecoinDir, "data", "merkleeyes.db"), EyesCacheSize) + eyesCli = eyes.NewLocalClient(path.Join(rootDir, "data", "merkleeyes.db"), EyesCacheSize) } else { var err error eyesCli, err = eyes.NewClient(eyesFlag) @@ -65,6 +68,7 @@ func startCmd(cmd *cobra.Command, args []string) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) + basecoinApp.SetLogger(logger.With("module", "app")) // register IBC plugn basecoinApp.RegisterPlugin(NewIBCPlugin()) @@ -78,7 +82,7 @@ func startCmd(cmd *cobra.Command, args []string) error { // else, assume it's been loaded if basecoinApp.GetState().GetChainID() == "" { // If genesis file exists, set key-value options - genesisFile := path.Join(basecoinDir, "genesis.json") + genesisFile := path.Join(rootDir, "genesis.json") if _, err := os.Stat(genesisFile); err == nil { err := basecoinApp.LoadGenesis(genesisFile) if err != nil { @@ -91,23 +95,24 @@ func startCmd(cmd *cobra.Command, args []string) error { chainID := basecoinApp.GetState().GetChainID() if withoutTendermintFlag { - log.Notice("Starting Basecoin without Tendermint", "chain_id", chainID) + logger.Info("Starting Basecoin without Tendermint", "chain_id", chainID) // run just the abci app/server return startBasecoinABCI(basecoinApp) } else { - log.Notice("Starting Basecoin with Tendermint", "chain_id", chainID) + logger.Info("Starting Basecoin with Tendermint", "chain_id", chainID) // start the app with tendermint in-process - return startTendermint(basecoinDir, basecoinApp) + return startTendermint(rootDir, basecoinApp) } } func startBasecoinABCI(basecoinApp *app.Basecoin) error { - // Start the ABCI listener svr, err := server.NewServer(addrFlag, "socket", basecoinApp) if err != nil { return errors.Errorf("Error creating listener: %v\n", err) } + svr.SetLogger(logger.With("module", "abci-server")) + svr.Start() // Wait forever cmn.TrapSignal(func() { @@ -117,27 +122,41 @@ func startBasecoinABCI(basecoinApp *app.Basecoin) error { return nil } +func getTendermintConfig() (*config.Config, error) { + cfg := config.DefaultConfig() + err := viper.Unmarshal(cfg) + if err != nil { + return nil, err + } + cfg.SetRoot(cfg.RootDir) + config.EnsureRoot(cfg.RootDir) + return cfg, nil +} + func startTendermint(dir string, basecoinApp *app.Basecoin) error { - - // Get configuration - tmConfig := tmcfg.GetConfig(dir) - // logger.SetLogLevel("notice") //config.GetString("log_level")) - // parseFlags(config, args[1:]) // Command line overrides - - // Create & start tendermint node - privValidatorFile := tmConfig.GetString("priv_validator_file") - privValidator := tmtypes.LoadOrGenPrivValidator(privValidatorFile) - n := node.NewNode(tmConfig, privValidator, proxy.NewLocalClientCreator(basecoinApp)) - - _, err := n.Start() + cfg, err := getTendermintConfig() if err != nil { return err } - // Wait forever - cmn.TrapSignal(func() { - // Cleanup - n.Stop() - }) + // TODO: parse the log level from the config properly (multi modules) + // but some tm code must be refactored for better usability + lvl, err := log.AllowLevel(cfg.LogLevel) + if err != nil { + return err + } + tmLogger := log.NewFilter(logger, lvl) + + // Create & start tendermint node + privValidator := types.LoadOrGenPrivValidator(cfg.PrivValidatorFile(), tmLogger) + n := node.NewNode(cfg, privValidator, proxy.NewLocalClientCreator(basecoinApp), tmLogger.With("module", "node")) + + _, err = n.Start() + if err != nil { + return err + } + + // Trap signal, run forever. + n.RunForever() return nil } diff --git a/cmd/commands/tx.go b/cmd/commands/tx.go index 2e12be06e..d13411bf0 100644 --- a/cmd/commands/tx.go +++ b/cmd/commands/tx.go @@ -3,16 +3,15 @@ package commands import ( "encoding/hex" "fmt" + "strings" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/tendermint/basecoin/types" - crypto "github.com/tendermint/go-crypto" - client "github.com/tendermint/go-rpc/client" wire "github.com/tendermint/go-wire" - ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/rpc/client" ) //commands @@ -60,7 +59,7 @@ func init() { {&fromFlag, "from", "key.json", "Path to a private key to sign the transaction"}, {&amountFlag, "amount", "", "Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver)"}, {&gasFlag, "gas", 0, "The amount of gas for the transaction"}, - {&feeFlag, "fee", "", "Coins for the transaction fee of the format "}, + {&feeFlag, "fee", "0coin", "Coins for the transaction fee of the format "}, {&seqFlag, "sequence", -1, "Sequence number for the account (-1 to autocalculate)"}, } @@ -83,12 +82,29 @@ func init() { func sendTxCmd(cmd *cobra.Command, args []string) error { + var toHex string + var chainPrefix string + spl := strings.Split(toFlag, "/") + switch len(spl) { + case 1: + toHex = spl[0] + case 2: + chainPrefix = spl[0] + toHex = spl[1] + default: + return errors.Errorf("To address has too many slashes") + } + // convert destination address to bytes - to, err := hex.DecodeString(StripHex(toFlag)) + to, err := hex.DecodeString(StripHex(toHex)) if err != nil { return errors.Errorf("To address is invalid hex: %v\n", err) } + if chainPrefix != "" { + to = []byte(chainPrefix + "/" + string(to)) + } + // load the priv key privKey, err := LoadKey(fromFlag) if err != nil { @@ -123,7 +139,7 @@ func sendTxCmd(cmd *cobra.Command, args []string) error { // sign that puppy signBytes := tx.SignBytes(chainIDFlag) - tx.Inputs[0].Signature = crypto.SignatureS{privKey.Sign(signBytes)} + tx.Inputs[0].Signature = privKey.Sign(signBytes) fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) @@ -179,7 +195,7 @@ func AppTx(name string, data []byte) error { Data: data, } - tx.Input.Signature = crypto.SignatureS{privKey.Sign(tx.SignBytes(chainIDFlag))} + tx.Input.Signature = privKey.Sign(tx.SignBytes(chainIDFlag)) fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) @@ -194,23 +210,17 @@ func AppTx(name string, data []byte) error { // broadcast the transaction to tendermint func broadcastTx(tx types.Tx) ([]byte, string, error) { - - tmResult := new(ctypes.TMResult) - uriClient := client.NewURIClient(txNodeFlag) - + httpClient := client.NewHTTP(txNodeFlag, "/websocket") // Don't you hate having to do this? // How many times have I lost an hour over this trick?! txBytes := []byte(wire.BinaryBytes(struct { types.Tx `json:"unwrap"` }{tx})) - - _, err := uriClient.Call("broadcast_tx_commit", map[string]interface{}{"tx": txBytes}, tmResult) + res, err := httpClient.BroadcastTxCommit(txBytes) if err != nil { return nil, "", errors.Errorf("Error on broadcast tx: %v", err) } - res := (*tmResult).(*ctypes.ResultBroadcastTxCommit) - // if it fails check, we don't even get a delivertx back! if !res.CheckTx.Code.IsOK() { r := res.CheckTx @@ -228,12 +238,12 @@ func broadcastTx(tx types.Tx) ([]byte, string, error) { // if the sequence flag is set, return it; // else, fetch the account by querying the app and return the sequence number func getSeq(address []byte) (int, error) { - if seqFlag >= 0 { return seqFlag, nil } - acc, err := getAcc(txNodeFlag, address) + httpClient := client.NewHTTP(txNodeFlag, "/websocket") + acc, err := getAccWithClient(httpClient, address) if err != nil { return 0, err } diff --git a/cmd/commands/utils.go b/cmd/commands/utils.go index 0f263d82a..dc0b5f162 100644 --- a/cmd/commands/utils.go +++ b/cmd/commands/utils.go @@ -3,52 +3,20 @@ package commands import ( "encoding/hex" "fmt" - "os" - "path" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/tendermint/basecoin/state" + abci "github.com/tendermint/abci/types" + wire "github.com/tendermint/go-wire" + "github.com/tendermint/basecoin/types" - abci "github.com/tendermint/abci/types" - cmn "github.com/tendermint/go-common" - client "github.com/tendermint/go-rpc/client" - wire "github.com/tendermint/go-wire" - ctypes "github.com/tendermint/tendermint/rpc/core/types" + client "github.com/tendermint/tendermint/rpc/client" tmtypes "github.com/tendermint/tendermint/types" ) -//This variable can be overwritten by plugin applications -// if they require a different working directory -var DefaultHome = ".basecoin" - -func BasecoinRoot(rootDir string) string { - if rootDir == "" { - rootDir = os.Getenv("BCHOME") - } - if rootDir == "" { - rootDir = path.Join(os.Getenv("HOME"), DefaultHome) - } - return rootDir -} - -//Add debugging flag and execute the root command -func ExecuteWithDebug(RootCmd *cobra.Command) { - - var debug bool - RootCmd.SilenceUsage = true - RootCmd.PersistentFlags().BoolVar(&debug, "debug", false, "enables stack trace error messages") - - //note that Execute() prints the error if encountered, so no need to reprint the error, - // only if we want the full stack trace - if err := RootCmd.Execute(); err != nil && debug { - cmn.Exit(fmt.Sprintf("%+v\n", err)) - } -} - //Quickly registering flags can be quickly achieved through using the utility functions //RegisterFlags, and RegisterPersistentFlags. Ex: // flags := []Flag2Register{ @@ -130,31 +98,27 @@ func StripHex(s string) string { return s } -func Query(tmAddr string, key []byte) (*abci.ResponseQuery, error) { - uriClient := client.NewURIClient(tmAddr) - tmResult := new(ctypes.TMResult) +func Query(tmAddr string, key []byte) (*abci.ResultQuery, error) { + httpClient := client.NewHTTP(tmAddr, "/websocket") + return queryWithClient(httpClient, key) +} - params := map[string]interface{}{ - "path": "/key", - "data": key, - "prove": true, - } - _, err := uriClient.Call("abci_query", params, tmResult) +func queryWithClient(httpClient *client.HTTP, key []byte) (*abci.ResultQuery, error) { + res, err := httpClient.ABCIQuery("/key", key, true) if err != nil { return nil, errors.Errorf("Error calling /abci_query: %v", err) } - res := (*tmResult).(*ctypes.ResultABCIQuery) - if !res.Response.Code.IsOK() { - return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Response.Code, res.Response.Log) + if !res.Code.IsOK() { + return nil, errors.Errorf("Query got non-zero exit code: %v. %s", res.Code, res.Log) } - return &res.Response, nil + return res.ResultQuery, nil } // fetch the account by querying the app -func getAcc(tmAddr string, address []byte) (*types.Account, error) { +func getAccWithClient(httpClient *client.HTTP, address []byte) (*types.Account, error) { - key := state.AccountKey(address) - response, err := Query(tmAddr, key) + key := types.AccountKey(address) + response, err := queryWithClient(httpClient, key) if err != nil { return nil, err } @@ -176,17 +140,33 @@ func getAcc(tmAddr string, address []byte) (*types.Account, error) { } func getHeaderAndCommit(tmAddr string, height int) (*tmtypes.Header, *tmtypes.Commit, error) { - tmResult := new(ctypes.TMResult) - uriClient := client.NewURIClient(tmAddr) - - method := "commit" - _, err := uriClient.Call(method, map[string]interface{}{"height": height}, tmResult) + httpClient := client.NewHTTP(tmAddr, "/websocket") + res, err := httpClient.Commit(height) if err != nil { - return nil, nil, errors.Errorf("Error on %s: %v", method, err) + return nil, nil, errors.Errorf("Error on commit: %v", err) } - resCommit := (*tmResult).(*ctypes.ResultCommit) - header := resCommit.Header - commit := resCommit.Commit + header := res.Header + commit := res.Commit return header, commit, nil } + +func waitForBlock(httpClient *client.HTTP) error { + res, err := httpClient.Status() + if err != nil { + return err + } + + lastHeight := res.LatestBlockHeight + for { + res, err := httpClient.Status() + if err != nil { + return err + } + if res.LatestBlockHeight > lastHeight { + break + } + + } + return nil +} diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 8a96b50ed..083535733 100644 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -1,19 +1,22 @@ package main import ( + "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/tmlibs/cli" ) func main() { - var RootCmd = &cobra.Command{ Use: "counter", Short: "demo plugin for basecoin", } RootCmd.AddCommand( + commands.InitCmd, commands.StartCmd, commands.TxCmd, commands.QueryCmd, @@ -21,8 +24,10 @@ func main() { commands.VerifyCmd, commands.BlockCmd, commands.AccountCmd, - commands.QuickVersionCmd("0.1.0"), + commands.UnsafeResetAllCmd, + commands.VersionCmd, ) - commands.ExecuteWithDebug(RootCmd) + cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin")) + cmd.Execute() } diff --git a/demo/data/chain1/config.toml b/demo/data/chain1/config.toml index 9b97202d1..e2bcb49ff 100644 --- a/demo/data/chain1/config.toml +++ b/demo/data/chain1/config.toml @@ -7,5 +7,5 @@ node_laddr = "tcp://0.0.0.0:46656" seeds = "" fast_sync = true db_backend = "leveldb" -log_level = "notice" +log_level = "info" rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain1/genesis.json b/demo/data/chain1/genesis.json index d50161a5a..284572eb3 100644 --- a/demo/data/chain1/genesis.json +++ b/demo/data/chain1/genesis.json @@ -6,10 +6,10 @@ { "amount": 10, "name": "", - "pub_key": [ - 1, - "D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" - ] + "pub_key": { + "type": "ed25519", + "data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" + } } ], "app_options": { diff --git a/demo/data/chain1/priv_validator.json b/demo/data/chain1/priv_validator.json index 1ea10c12d..55db06f4b 100644 --- a/demo/data/chain1/priv_validator.json +++ b/demo/data/chain1/priv_validator.json @@ -1,16 +1 @@ -{ - "address": "EBB0B4A899973C524A6BB18A161056A55F590F41", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "5FFDC1EA5FA2CA4A0A5503C86D2D348C5B401AD80FAA1899508F1ED00D8982E8D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" - ], - "pub_key": [ - 1, - "D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" - ] -} \ No newline at end of file +{"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"}} \ No newline at end of file diff --git a/demo/data/chain2/config.toml b/demo/data/chain2/config.toml index 9b97202d1..e2bcb49ff 100644 --- a/demo/data/chain2/config.toml +++ b/demo/data/chain2/config.toml @@ -7,5 +7,5 @@ node_laddr = "tcp://0.0.0.0:46656" seeds = "" fast_sync = true db_backend = "leveldb" -log_level = "notice" +log_level = "info" rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain2/genesis.json b/demo/data/chain2/genesis.json index c53461756..3ff94b993 100644 --- a/demo/data/chain2/genesis.json +++ b/demo/data/chain2/genesis.json @@ -6,10 +6,10 @@ { "amount": 10, "name": "", - "pub_key": [ - 1, - "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" - ] + "pub_key": { + "type": "ed25519", + "data": "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" + } } ], "app_options": { diff --git a/demo/data/chain2/priv_validator.json b/demo/data/chain2/priv_validator.json index 8b3eb7e34..12eb62525 100644 --- a/demo/data/chain2/priv_validator.json +++ b/demo/data/chain2/priv_validator.json @@ -1,16 +1 @@ -{ - "address": "D42CFCB9C42DF9A73143EEA89255D1DF027B6240", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "6353FAF4ADEB03EA496A9EAE5BE56C4C6A851CB705401788184FDC9198413C2C9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" - ], - "pub_key": [ - 1, - "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" - ] -} \ No newline at end of file +{"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"}} \ No newline at end of file diff --git a/demo/start.sh b/demo/start.sh index 3bd214a03..490806fd6 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -4,7 +4,8 @@ set -e cd $GOPATH/src/github.com/tendermint/basecoin/demo LOG_DIR="." -TM_VERSION="v0.9.2" +TM_VERSION="develop" +#TM_VERSION="v0.10.0" if [[ "$CIRCLECI" == "true" ]]; then # set log dir @@ -23,6 +24,13 @@ fi set -u +function ifExit() { + if [[ "$?" != 0 ]]; then + echo "FAIL" + exit 1 + fi +} + function removeQuotes() { temp="${1%\"}" temp="${temp#\"}" @@ -41,7 +49,7 @@ function waitForNode() { exit 1 fi echo "...... still waiting on $addr" - sleep 1 + sleep 1 curl -s $addr/status > /dev/null ERR=$? i=$((i+1)) @@ -52,12 +60,12 @@ function waitForNode() { function waitForBlock() { addr=$1 - b1=`curl -s $addr/status | jq .result[1].latest_block_height` + b1=`curl -s $addr/status | jq .result.latest_block_height` b2=$b1 while [ "$b2" == "$b1" ]; do echo "Waiting for node $addr to commit a block ..." sleep 1 - b2=`curl -s $addr/status | jq .result[1].latest_block_height` + b2=`curl -s $addr/status | jq .result.latest_block_height` done } @@ -83,12 +91,16 @@ echo "" echo "... starting chains" echo "" # start the first node -TMROOT=$BCHOME1 tendermint node --skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log & +TMROOT=$BCHOME1 tendermint node --p2p.skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log & +ifExit BCHOME=$BCHOME1 basecoin start --without-tendermint &> $LOG_DIR/chain1_basecoin.log & +ifExit # start the second node -TMROOT=$BCHOME2 tendermint node --skip_upnp --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log & +TMROOT=$BCHOME2 tendermint node --p2p.skip_upnp --log_level=info --p2p.laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log & +ifExit BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> $LOG_DIR/chain2_basecoin.log & +ifExit echo "" echo "... waiting for chains to start" @@ -99,25 +111,32 @@ waitForNode localhost:36657 # TODO: remove the sleep # Without it we sometimes get "Account bytes are empty for address: 053BA0F19616AFF975C8756A2CBFF04F408B4D47" -sleep 3 +sleep 3 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json +ifExit echo "" echo "... creating egress packet on chain1" echo "" -# create a packet on chain1 destined for chain2 -PAYLOAD="DEADBEEF" #TODO -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 1 +# send coins from chain1 to an address on chain2 +# TODO: dont hardcode the address +basecoin tx send --amount 10mycoin $CHAIN_FLAGS1 --to $CHAIN_ID2/053BA0F19616AFF975C8756A2CBFF04F408B4D47 +ifExit + +# alternative way to create packets (for testing) +# basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --ibc_sequence 0 echo "" echo "... querying for packet data" echo "" # query for the packet data and proof -QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +# since we only sent one packet, the sequence number is 0 +QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,0) +ifExit HEIGHT=$(echo $QUERY_RESULT | jq .height) PACKET=$(echo $QUERY_RESULT | jq .value) PROOF=$(echo $QUERY_RESULT | jq .proof) @@ -143,7 +162,8 @@ echo "" echo "... querying for block data" echo "" # get the header and commit for the height -HEADER_AND_COMMIT=$(basecoin block $HEIGHT) +HEADER_AND_COMMIT=$(basecoin block $HEIGHT) +ifExit HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) HEADER=$(removeQuotes $HEADER) COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) @@ -158,18 +178,21 @@ echo "... updating state of chain1 on chain2" echo "" # update the state of chain1 on chain2 basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT +ifExit echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height $HEIGHT --packet 0x$PACKET --proof 0x$PROOF +ifExit echo "" echo "... checking if the packet is present on chain2" echo "" # query for the packet on chain2 -basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 +basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,0 +ifExit echo "" echo "DONE!" diff --git a/docs/go_basics.md b/docs/go_basics.md index 1364bcb05..4744b1bff 100644 --- a/docs/go_basics.md +++ b/docs/go_basics.md @@ -98,7 +98,7 @@ make install make test ``` -Great! Now when I run `tendermint` I have the newest of the new, the develop branch! But please not that this branch is not considered production ready and may have issues. This should only be done if you want to develop code for the future and run locally. +Great! Now when I run `tendermint` I have the newest of the new, the develop branch! But please note that this branch is not considered production ready and may have issues. This should only be done if you want to develop code for the future and run locally. But wait, I want to mix and match. There is a bugfix in `go-p2p:persistent_peer` that I want to use with tendermint. How to compile this. I will show with a simple example, please update the repo and commit numbers for your usecase. Also, make sure these branches are compatible, so if `persistent_peer` is close to `master` it should work. But if it is 15 commits ahead, you will probably need the `develop` branch of tendermint to compile with it. But I assume you know your way around git and can figure that out. @@ -129,4 +129,3 @@ Great, now you just compiled the master branch of tendermint along with the bugf Okay, that's it, with this info you should be able to follow along and trouble-shoot any issues you have with the rest of the guide. - diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index fb2e50291..c2b974df9 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -111,7 +111,7 @@ type Coin struct { Accounts are serialized and stored in a Merkle tree under the key `base/a/
`, where `
` is the address of the account. Typically, the address of the account is the 20-byte `RIPEMD160` hash of the public key, but other formats are acceptable as well, -as defined in the [tendermint crypto library](https://github.com/tendermint/go-crypto). +as defined in the [Tendermint crypto library](https://github.com/tendermint/go-crypto). The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle). ## Transactions @@ -150,8 +150,8 @@ This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent, and the `GasPrice` is implicit. -In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering -of transactions, like in bitcoin. And the `Gas` is meant to be used by the application +In Basecoin, the `Fee` is meant to be used by the validators to inform the ordering +of transactions, like in Bitcoin. And the `Gas` is meant to be used by the application plugin to control its execution. There is currently no means to pass `Fee` information to the Tendermint validators, but it will come soon... diff --git a/docs/guide/basecoin-plugins.md b/docs/guide/basecoin-plugins.md index cc78d246a..4e1c2251c 100644 --- a/docs/guide/basecoin-plugins.md +++ b/docs/guide/basecoin-plugins.md @@ -144,7 +144,7 @@ to whatever your plugin tool is going to be called. Next is the `cmd.go`. This is where we extend the tool with any new commands and flags we need to send transactions to our plugin. Note the `init()` function, where we register a new transaction subcommand with `RegisterTxSubcommand`, -and where we load the plugin into the basecoin app with `RegisterStartPlugin`. +and where we load the plugin into the Basecoin app with `RegisterStartPlugin`. Finally is the `plugin.go`, where we provide an implementation of the `Plugin` interface. The most important part of the implementation is the `RunTx` method, which determines the meaning of the data diff --git a/docs/guide/basecoin-tool.md b/docs/guide/basecoin-tool.md index 52f42d832..53d8df037 100644 --- a/docs/guide/basecoin-tool.md +++ b/docs/guide/basecoin-tool.md @@ -1,7 +1,7 @@ # The Basecoin Tool -In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guides/basecoin-basics) -and [how to implement a plugin](/docs/guides/example-plugin). +In previous tutorials we learned the [basics of the `basecoin` CLI](/docs/guide/basecoin-basics.md) +and [how to implement a plugin](/docs/guide/basecoin-plugins.md). In this tutorial, we provide more details on using the `basecoin` tool. # Data Directory @@ -14,7 +14,7 @@ basecoin init basecoin start ``` -or +or ``` BCHOME=~/.my_basecoin_data basecoin init @@ -33,7 +33,7 @@ basecoin init This will create a single `genesis.json` file in `~/.basecoin` with the information for both Basecoin and Tendermint. -Now, In one window, run +Now, In one window, run ``` basecoin start --without-tendermint @@ -147,7 +147,7 @@ basecoin unsafe_reset_all Any required plugin initialization should be constructed using `SetOption` on genesis. When starting a new chain for the first time, `SetOption` will be called for each item the genesis file. Within genesis.json file entries are made in the format: `"/", ""`, where `` is the plugin name, -and `` and `` are the strings passed into the plugin SetOption function. -This function is intended to be used to set plugin specific information such +and `` and `` are the strings passed into the plugin SetOption function. +This function is intended to be used to set plugin specific information such as the plugin state. diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go index 11a6f6bc0..b43917626 100644 --- a/docs/guide/src/example-plugin/cmd.go +++ b/docs/guide/src/example-plugin/cmd.go @@ -24,8 +24,6 @@ var ( //Called during CLI initialization func init() { - commands.DefaultHome = ".basecoin-example-plugin" - //Set the Plugin Flags ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") diff --git a/docs/guide/src/example-plugin/main.go b/docs/guide/src/example-plugin/main.go index e2892bd7d..328e22025 100644 --- a/docs/guide/src/example-plugin/main.go +++ b/docs/guide/src/example-plugin/main.go @@ -1,9 +1,12 @@ package main import ( + "os" + "github.com/spf13/cobra" "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/tmlibs/cli" ) func main() { @@ -27,6 +30,6 @@ func main() { commands.UnsafeResetAllCmd, ) - //Run the root command - commands.ExecuteWithDebug(RootCmd) + cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin-example-plugin")) + cmd.Execute() } diff --git a/glide.lock b/glide.lock index 4c67c14d6..059bdc1dc 100644 --- a/glide.lock +++ b/glide.lock @@ -1,39 +1,92 @@ -hash: c6e5febc35b5fd1003066820defb8a089db048b407239dad9faf44553fdc15e8 -updated: 2017-04-21T12:55:42.7004558-04:00 +hash: 9d06ae13959cbb2835f5ae400a4b65e4bc329a567c949aec4aeab318c271da39 +updated: 2017-05-24T15:11:32.643553723+02:00 imports: +- name: github.com/bgentry/speakeasy + version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd - name: github.com/btcsuite/btcd - version: 4b348c1d33373d672edd83fc576892d0e46686d2 + version: b8df516b4b267acf2de46be593a9d948d1d2c420 subpackages: - btcec +- name: github.com/btcsuite/fastsha256 + version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/BurntSushi/toml version: b26d9c308763d68093482582cea63d69be07a0f0 - name: github.com/ebuchman/fail-test version: 95f809107225be108efcf10a3509e4ea6ceef3c4 +- name: github.com/fsnotify/fsnotify + version: 4da3e2cfbabc9f751898f250b49f2439785783a1 +- name: github.com/go-kit/kit + version: d67bb4c202e3b91377d1079b110a6c9ce23ab2f8 + subpackages: + - log + - log/level + - log/term +- name: github.com/go-logfmt/logfmt + version: 390ab7935ee28ec6b286364bba9b4dd6410cb3d5 +- name: github.com/go-playground/locales + version: 1e5f1161c6416a5ff48840eb8724a394e48cc534 + subpackages: + - currency +- name: github.com/go-playground/universal-translator + version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/golang/protobuf - version: 2bba0603135d7d7f5cb73b2125beeda19c09f4ef + version: 18c9bb3261723cd5401db4d0c9fbc5c3b6c70fe8 subpackages: - proto - ptypes/any - name: github.com/golang/snappy version: 553a641470496b2327abcac10b36396bd98e45c9 +- name: github.com/gorilla/context + version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 +- name: github.com/gorilla/handlers + version: 3a5767ca75ece5f7f1440b1d16975247f8d8b221 +- name: github.com/gorilla/mux + version: bcd8bc72b08df0f70df986b97f95590779502d31 - name: github.com/gorilla/websocket - version: 3ab3a8b8831546bd18fd182c20687ca853b2bb13 + version: a91eba7f97777409bc2c443f5534d41dd20c5720 +- name: github.com/hashicorp/hcl + version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca + subpackages: + - hcl/ast + - hcl/parser + - hcl/scanner + - hcl/strconv + - hcl/token + - json/parser + - json/scanner + - json/token - name: github.com/inconshreveable/mousetrap version: 76626ae9c91c4f2a10f34cad8ce83ea42c93bb75 - name: github.com/jmhodges/levigo version: c42d9e0ca023e2198120196f842701bb4c55d7b9 -- name: github.com/mattn/go-colorable - version: ded68f7a9561c023e790de24279db7ebf473ea80 -- name: github.com/mattn/go-isatty - version: fc9e8d8ef48496124e79ae0df75490096eccf6fe +- name: github.com/kr/logfmt + version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 +- name: github.com/magiconair/properties + version: 51463bfca2576e06c62a8504b5c0f06d61312647 +- name: github.com/mitchellh/mapstructure + version: cc8532a8e9a55ea36402aa21efdf403a60d34096 +- name: github.com/pelletier/go-buffruneio + version: c37440a7cf42ac63b919c752ca73a85067e05992 +- name: github.com/pelletier/go-toml + version: 13d49d4606eb801b8f01ae542b4afc4c6ee3d84a - name: github.com/pkg/errors version: ff09b135c25aae272398c51a07235b90a75aa4f0 +- name: github.com/spf13/afero + version: 9be650865eab0c12963d8753212f4f9c66cdcf12 + subpackages: + - mem +- name: github.com/spf13/cast + version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 - name: github.com/spf13/cobra - version: 10f6b9d7e1631a54ad07c5c0fb71c28a1abfd3c2 + version: 3454e0e28e69c1b8effa6b5123c8e4185e20d696 +- name: github.com/spf13/jwalterweatherman + version: 8f07c835e5cc1450c082fe3a439cf87b0cbb2d99 - name: github.com/spf13/pflag - version: 2300d0f8576fe575f71aaa5b9bbe4e1b0dc2eb51 + version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 +- name: github.com/spf13/viper + version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2 - name: github.com/syndtr/goleveldb version: 8c81ea47d4c41a385645e133e15510fc6a2a74b4 subpackages: @@ -50,7 +103,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: 56e13d87f4e3ec1ea756957d6b23caa6ebcf0998 + version: 5dabeffb35c027d7087a12149685daa68989168b subpackages: - client - example/dummy @@ -61,72 +114,80 @@ imports: subpackages: - edwards25519 - extra25519 -- name: github.com/tendermint/go-autofile - version: 48b17de82914e1ec2f134ce823ba426337d2c518 -- name: github.com/tendermint/go-clist - version: 3baa390bbaf7634251c42ad69a8682e7e3990552 -- name: github.com/tendermint/go-common - version: f9e3db037330c8a8d61d3966de8473eaf01154fa -- name: github.com/tendermint/go-config - version: 620dcbbd7d587cf3599dedbf329b64311b0c307a - name: github.com/tendermint/go-crypto - version: 0ca2c6fdb0706001ca4c4b9b80c9f428e8cf39da -- name: github.com/tendermint/go-data - version: e7fcc6d081ec8518912fcdc103188275f83a3ee5 -- name: github.com/tendermint/go-db - version: 9643f60bc2578693844aacf380a7c32e4c029fee -- name: github.com/tendermint/go-events - version: f8ffbfb2be3483e9e7927495590a727f51c0c11f -- name: github.com/tendermint/go-flowrate - version: a20c98e61957faa93b4014fbd902f20ab9317a6a + version: 438b16f1f84ef002d7408ecd6fc3a3974cbc9559 subpackages: - - flowrate -- name: github.com/tendermint/go-logger - version: cefb3a45c0bf3c493a04e9bcd9b1540528be59f2 -- name: github.com/tendermint/go-merkle - version: 714d4d04557fd068a7c2a1748241ce8428015a96 -- name: github.com/tendermint/go-p2p - version: 17124989a93774833df33107fbf17157a7f8ef31 - subpackages: - - upnp -- name: github.com/tendermint/go-rpc - version: 559613689d56eaa423b19a3a4158546beb4857de - subpackages: - - client - - server - - types + - cmd + - keys + - keys/cryptostore + - keys/server + - keys/server/types + - keys/storage/filestorage - name: github.com/tendermint/go-wire - version: c1c9a57ab8038448ddea1714c0698f8051e5748c -- name: github.com/tendermint/log15 - version: ae0f3d6450da9eac7074b439c8e1c3cabf0d5ce6 + version: 97beaedf0f4dbc035309157c92be3b30cc6e5d74 subpackages: - - term + - data + - data/base58 +- name: github.com/tendermint/light-client + version: 478876ca34b360df62f941d5e20cdd608fa0a466 + subpackages: + - certifiers + - certifiers/client + - certifiers/files + - commands + - commands/proofs + - commands/proxy + - commands/seeds + - commands/txs + - proofs - name: github.com/tendermint/merkleeyes - version: 9fb76efa5aebe773a598f97e68e75fe53d520e70 + version: c722818b460381bc5b82e38c73ff6e22a9df624d subpackages: - app - client + - iavl - name: github.com/tendermint/tendermint - version: 6bcd4242f1f336e2b2ef4f644fabaf56222b34d0 + version: 11b5d11e9eec170e1d3dce165f0270d5c0759d69 subpackages: - blockchain - cmd/tendermint/commands - - config/tendermint + - cmd/tendermint/commands/flags + - config - consensus - mempool - node + - p2p + - p2p/upnp - proxy + - rpc/client - rpc/core - rpc/core/types - rpc/grpc + - rpc/lib + - rpc/lib/client + - rpc/lib/server + - rpc/lib/types - state - state/txindex - state/txindex/kv - state/txindex/null - types - version +- name: github.com/tendermint/tmlibs + version: 8af1c70a8be17543eb33e9bfbbcdd8371e3201cc + subpackages: + - autofile + - cli + - clist + - common + - db + - events + - flowrate + - log + - logger + - merkle - name: golang.org/x/crypto - version: 96846453c37f0876340a66a47f3f75b1f3a6cd2d + version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e subpackages: - curve25519 - nacl/box @@ -137,7 +198,7 @@ imports: - ripemd160 - salsa20/salsa - name: golang.org/x/net - version: c8c74377599bd978aee1cf3b9b63a8634051cec2 + version: feeb485667d1fdabe727840fe00adc22431bc86e subpackages: - context - http2 @@ -147,11 +208,11 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: ea9bcade75cb975a0b9738936568ab388b845617 + version: 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03 subpackages: - unix - name: golang.org/x/text - version: 19e3104b43db45fca0303f489a9536087b184802 + version: 470f45bf29f4147d6fbd7dfd0a02a848e49f5bf4 subpackages: - secure/bidirule - transform @@ -162,10 +223,11 @@ imports: subpackages: - googleapis/rpc/status - name: google.golang.org/grpc - version: 6914ab1e338c92da4218a23d27fcd03d0ad78d46 + version: 844f573616520565fdc6fb4db242321b5456fd6d subpackages: - codes - credentials + - grpclb/grpc_lb_v1 - grpclog - internal - keepalive @@ -176,6 +238,10 @@ imports: - status - tap - transport +- name: gopkg.in/go-playground/validator.v9 + version: 6d8c18553ea1ac493d049edd6f102f52e618f085 +- name: gopkg.in/yaml.v2 + version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b testImports: - name: github.com/davecgh/go-spew version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 diff --git a/glide.yaml b/glide.yaml index 87735c19c..abc395a69 100644 --- a/glide.yaml +++ b/glide.yaml @@ -1,24 +1,53 @@ package: github.com/tendermint/basecoin import: -- package: github.com/tendermint/go-common - version: develop +- package: github.com/gorilla/websocket +- package: github.com/pkg/errors +- package: github.com/spf13/cobra +- package: github.com/spf13/pflag +- package: github.com/spf13/viper +- package: github.com/tendermint/abci + subpackages: + - server + - types - package: github.com/tendermint/go-crypto - version: develop -- package: github.com/tendermint/go-events - version: develop -- package: github.com/tendermint/go-logger - version: develop -- package: github.com/tendermint/go-data - version: develop -- package: github.com/tendermint/go-rpc - version: develop + subpackages: + - cmd + - keys - package: github.com/tendermint/go-wire + subpackages: + - data +- package: github.com/tendermint/light-client version: develop + subpackages: + - commands + - commands/proofs + - commands/seeds + - commands/txs + - proofs - package: github.com/tendermint/merkleeyes - version: develop + subpackages: + - client + - iavl - package: github.com/tendermint/tendermint version: develop -- package: github.com/tendermint/abci - version: develop -- package: github.com/gorilla/websocket - version: v1.1.0 + subpackages: + - config + - node + - proxy + - rpc/client + - rpc/core/types + - rpc/lib/client + - rpc/lib/types + - types +- package: github.com/tendermint/tmlibs + subpackages: + - cli + - common + - events + - log + - logger +testImport: +- package: github.com/stretchr/testify + subpackages: + - assert + - require diff --git a/plugins/counter/counter_test.go b/plugins/counter/counter_test.go index f7c658a9a..f1c7d9d35 100644 --- a/plugins/counter/counter_test.go +++ b/plugins/counter/counter_test.go @@ -9,7 +9,6 @@ import ( abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/types" - crypto "github.com/tendermint/go-crypto" "github.com/tendermint/go-wire" eyescli "github.com/tendermint/merkleeyes/client" ) @@ -52,8 +51,7 @@ func TestCounterPlugin(t *testing.T) { // Sign request signBytes := tx.SignBytes(chainID) // t.Logf("Sign bytes: %X\n", signBytes) - sig := test1PrivAcc.Sign(signBytes) - tx.Input.Signature = crypto.SignatureS{sig} + tx.Input.Signature = test1PrivAcc.Sign(signBytes) // t.Logf("Signed TX bytes: %X\n", wire.BinaryBytes(struct{ types.Tx }{tx})) // Write request diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index 25f5a6da8..eb8a27f1b 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -2,15 +2,19 @@ package ibc import ( "bytes" + "encoding/json" "errors" + "fmt" "net/url" + "strconv" "strings" abci "github.com/tendermint/abci/types" - "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - merkle "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" + merkle "github.com/tendermint/merkleeyes/iavl" + cmn "github.com/tendermint/tmlibs/common" + + "github.com/tendermint/basecoin/types" tm "github.com/tendermint/tendermint/types" ) @@ -51,8 +55,108 @@ type Packet struct { SrcChainID string DstChainID string Sequence uint64 - Type string - Payload []byte + Type string // redundant now that Type() is a method on Payload ? + Payload Payload +} + +func NewPacket(src, dst string, seq uint64, payload Payload) Packet { + return Packet{ + SrcChainID: src, + DstChainID: dst, + Sequence: seq, + Type: payload.Type(), + Payload: payload, + } +} + +// GetSequenceNumber gets the sequence number for packets being sent from the src chain to the dst chain. +// The sequence number counts how many packets have been sent. +// The next packet must include the latest sequence number. +func GetSequenceNumber(store types.KVStore, src, dst string) uint64 { + sequenceKey := toKey(_IBC, _EGRESS, src, dst) + seqBytes := store.Get(sequenceKey) + if seqBytes == nil { + return 0 + } + seq, err := strconv.ParseUint(string(seqBytes), 10, 64) + if err != nil { + cmn.PanicSanity(err.Error()) + } + return seq +} + +// SetSequenceNumber sets the sequence number for packets being sent from the src chain to the dst chain +func SetSequenceNumber(store types.KVStore, src, dst string, seq uint64) { + sequenceKey := toKey(_IBC, _EGRESS, src, dst) + store.Set(sequenceKey, []byte(strconv.FormatUint(seq, 10))) +} + +// SaveNewIBCPacket creates an IBC packet with the given payload from the src chain to the dst chain +// using the correct sequence number. It also increments the sequence number by 1 +func SaveNewIBCPacket(state types.KVStore, src, dst string, payload Payload) { + // fetch sequence number and increment by 1 + seq := GetSequenceNumber(state, src, dst) + SetSequenceNumber(state, src, dst, seq+1) + + // save ibc packet + packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq)) + packet := NewPacket(src, dst, uint64(seq), payload) + save(state, packetKey, packet) +} + +func GetIBCPacket(state types.KVStore, src, dst string, seq uint64) (Packet, error) { + packetKey := toKey(_IBC, _EGRESS, src, dst, cmn.Fmt("%v", seq)) + packetBytes := state.Get(packetKey) + + var packet Packet + err := wire.ReadBinaryBytes(packetBytes, &packet) + return packet, err +} + +//-------------------------------------------------------------------------------- + +const ( + PayloadTypeBytes = byte(0x01) + PayloadTypeCoins = byte(0x02) +) + +var _ = wire.RegisterInterface( + struct{ Payload }{}, + wire.ConcreteType{DataPayload{}, PayloadTypeBytes}, + wire.ConcreteType{CoinsPayload{}, PayloadTypeCoins}, +) + +type Payload interface { + AssertIsPayload() + Type() string + ValidateBasic() abci.Result +} + +func (DataPayload) AssertIsPayload() {} +func (CoinsPayload) AssertIsPayload() {} + +type DataPayload []byte + +func (p DataPayload) Type() string { + return "data" +} + +func (p DataPayload) ValidateBasic() abci.Result { + return abci.OK +} + +type CoinsPayload struct { + Address []byte + Coins types.Coins +} + +func (p CoinsPayload) Type() string { + return "coin" +} + +func (p CoinsPayload) ValidateBasic() abci.Result { + // TODO: validate + return abci.OK } //-------------------------------------------------------------------------------- @@ -202,9 +306,8 @@ func (sm *IBCStateMachine) runRegisterChainTx(tx IBCRegisterChainTx) { chainGen := tx.BlockchainGenesis // Parse genesis - var chainGenDoc = &tm.GenesisDoc{} - var err error - wire.ReadJSONPtr(&chainGenDoc, []byte(chainGen.Genesis), &err) + chainGenDoc := new(tm.GenesisDoc) + err := json.Unmarshal([]byte(chainGen.Genesis), chainGenDoc) if err != nil { sm.res.Code = IBCCodeEncodingError sm.res.Log = "Genesis doc couldn't be parsed: " + err.Error() @@ -298,8 +401,28 @@ func (sm *IBCStateMachine) runPacketCreateTx(tx IBCPacketCreateTx) { sm.res.Log = "Already exists" return } + + // Execute the payload + switch payload := tx.Packet.Payload.(type) { + case DataPayload: + // do nothing + case CoinsPayload: + // ensure enough coins were sent in tx to cover the payload coins + if !sm.ctx.Coins.IsGTE(payload.Coins) { + sm.res.Code = abci.CodeType_InsufficientFunds + sm.res.Log = fmt.Sprintf("Not enough funds sent in tx (%v) to send %v via IBC", sm.ctx.Coins, payload.Coins) + return + } + + // deduct coins from context + sm.ctx.Coins = sm.ctx.Coins.Minus(payload.Coins) + } + // Save new Packet save(sm.store, packetKey, packet) + + // set the sequence number + SetSequenceNumber(sm.store, packet.SrcChainID, packet.DstChainID, packet.Sequence) } func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { @@ -326,7 +449,7 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { return } - // Save new Packet + // Save new Packet (just for fun) save(sm.store, packetKeyIngress, packet) // Load Header and make sure it exists @@ -355,10 +478,24 @@ func (sm *IBCStateMachine) runPacketPostTx(tx IBCPacketPostTx) { ok := proof.Verify(packetKeyEgress, packetBytes, header.AppHash) if !ok { sm.res.Code = IBCCodeInvalidProof - sm.res.Log = "Proof is invalid" + sm.res.Log = fmt.Sprintf("Proof is invalid. key: %s; packetByes %X; header %v; proof %v", packetKeyEgress, packetBytes, header, proof) return } + // Execute payload + switch payload := packet.Payload.(type) { + case DataPayload: + // do nothing + case CoinsPayload: + // Add coins to destination account + acc := types.GetAccount(sm.store, payload.Address) + if acc == nil { + acc = &types.Account{} + } + acc.Balance = acc.Balance.Plus(payload.Coins) + types.SetAccount(sm.store, payload.Address, acc) + } + return } diff --git a/plugins/ibc/ibc_test.go b/plugins/ibc/ibc_test.go index 9439f4378..93a913dec 100644 --- a/plugins/ibc/ibc_test.go +++ b/plugins/ibc/ibc_test.go @@ -2,18 +2,22 @@ package ibc import ( "bytes" + "encoding/json" "sort" "strings" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/abci/types" - "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-merkle" "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" + "github.com/tendermint/merkleeyes/iavl" + cmn "github.com/tendermint/tmlibs/common" + + "github.com/tendermint/basecoin/types" tm "github.com/tendermint/tendermint/types" ) @@ -30,7 +34,7 @@ func genGenesisDoc(chainID string, numVals int) (*tm.GenesisDoc, []types.PrivAcc name := cmn.Fmt("%v_val_%v", chainID, i) privAcc := types.PrivAccountFromSecret(name) genDoc.Validators = append(genDoc.Validators, tm.GenesisValidator{ - PubKey: privAcc.PubKey.PubKey, + PubKey: privAcc.PubKey, Amount: 1, Name: name, }) @@ -64,23 +68,65 @@ func (pas PrivAccountsByAddress) Swap(i, j int) { //-------------------------------------------------------------------------------- -func TestIBCPlugin(t *testing.T) { - assert := assert.New(t) +var testGenesisDoc = `{ + "app_hash": "", + "chain_id": "test_chain_1", + "genesis_time": "0001-01-01T00:00:00.000Z", + "validators": [ + { + "amount": 10, + "name": "", + "pub_key": { + "type": "ed25519", + "data":"D6EBB92440CF375054AA59BCF0C99D596DEEDFFB2543CAE1BA1908B72CF9676A" + } + } + ], + "app_options": { + "accounts": [ + { + "pub_key": { + "type": "ed25519", + "data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" + }, + "coins": [ + { + "denom": "mycoin", + "amount": 9007199254740992 + } + ] + } + ] + } +}` + +func TestIBCGenesisFromString(t *testing.T) { + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.NewCallContext(nil, nil, types.Coins{}) + + registerChain(t, ibcPlugin, store, ctx, "test_chain", testGenesisDoc) +} + +//-------------------------------------------------------------------------------- + +func TestIBCPluginRegister(t *testing.T) { + require := require.New(t) eyesClient := eyes.NewLocalClient("", 0) store := types.NewKVCache(eyesClient) store.SetLogging() // Log all activity ibcPlugin := New() - ctx := types.CallContext{ - CallerAddress: nil, - CallerAccount: nil, - Coins: types.Coins{}, - } + ctx := types.NewCallContext(nil, nil, types.Coins{}) chainID_1 := "test_chain" - genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) - genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) + genDoc_1, _ := genGenesisDoc(chainID_1, 4) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) // Register a malformed chain res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ @@ -89,20 +135,10 @@ func TestIBCPlugin(t *testing.T) { Genesis: "", }, }})) - assert.Equal(IBCCodeEncodingError, res.Code) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, IBCCodeEncodingError) // Successfully register a chain - res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ - BlockchainGenesis{ - ChainID: "test_chain", - Genesis: string(genDocJSON_1), - }, - }})) - assert.True(res.IsOK(), res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) // Duplicate request fails res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ @@ -111,74 +147,82 @@ func TestIBCPlugin(t *testing.T) { Genesis: string(genDocJSON_1), }, }})) - assert.Equal(IBCCodeChainAlreadyExists, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, IBCCodeChainAlreadyExists) +} + +func TestIBCPluginPost(t *testing.T) { + require := require.New(t) + + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.NewCallContext(nil, nil, types.Coins{}) + + chainID_1 := "test_chain" + genDoc_1, _ := genGenesisDoc(chainID_1, 4) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) + + // Register a chain + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) // Create a new packet (for testing) - packet := Packet{ - SrcChainID: "test_chain", - DstChainID: "dst_chain", - Sequence: 0, - Type: "data", - Payload: []byte("hello world"), - } - res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ Packet: packet, }})) - assert.Equal(abci.CodeType_OK, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_OK) // Post a duplicate packet res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ Packet: packet, }})) - assert.Equal(IBCCodePacketAlreadyExists, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, IBCCodePacketAlreadyExists) +} + +func TestIBCPluginPayloadBytes(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.NewCallContext(nil, nil, types.Coins{}) + + chainID_1 := "test_chain" + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) + + // Register a chain + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) + + // Create a new packet (for testing) + packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet: packet, + }})) + assertAndLog(t, store, res, abci.CodeType_OK) // Construct a Header that includes the above packet. store.Sync() resCommit := eyesClient.CommitSync() appHash := resCommit.Data - header := tm.Header{ - ChainID: "test_chain", - Height: 999, - AppHash: appHash, - ValidatorsHash: []byte("must_exist"), // TODO make optional - } + header := newHeader("test_chain", 999, appHash, []byte("must_exist")) // Construct a Commit that signs above header - blockHash := header.Hash() - blockID := tm.BlockID{Hash: blockHash} - commit := tm.Commit{ - BlockID: blockID, - Precommits: make([]*tm.Vote, len(privAccs_1)), - } - for i, privAcc := range privAccs_1 { - vote := &tm.Vote{ - ValidatorAddress: privAcc.Account.PubKey.Address(), - ValidatorIndex: i, - Height: 999, - Round: 0, - Type: tm.VoteTypePrecommit, - BlockID: tm.BlockID{Hash: blockHash}, - } - vote.Signature = privAcc.PrivKey.Sign( - tm.SignBytes("test_chain", vote), - ) - commit.Precommits[i] = vote - } + commit := constructCommit(privAccs_1, header) // Update a chain res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ Header: header, Commit: commit, }})) - assert.Equal(abci.CodeType_OK, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_OK) // Get proof for the packet packetKey := toKey(_IBC, _EGRESS, @@ -192,7 +236,7 @@ func TestIBCPlugin(t *testing.T) { Prove: true, }) assert.Nil(err) - var proof *merkle.IAVLProof + var proof *iavl.IAVLProof err = wire.ReadBinaryBytes(resQuery.Proof, &proof) assert.Nil(err) @@ -203,169 +247,74 @@ func TestIBCPlugin(t *testing.T) { Packet: packet, Proof: proof, }})) - assert.Equal(abci.CodeType_OK, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_OK) } -func TestIBCPluginBadCommit(t *testing.T) { +func TestIBCPluginPayloadCoins(t *testing.T) { assert := assert.New(t) + require := require.New(t) eyesClient := eyes.NewLocalClient("", 0) store := types.NewKVCache(eyesClient) store.SetLogging() // Log all activity ibcPlugin := New() - ctx := types.CallContext{ - CallerAddress: nil, - CallerAccount: nil, - Coins: types.Coins{}, + coins := types.Coins{ + types.Coin{ + Denom: "mycoin", + Amount: 100, + }, } + ctx := types.NewCallContext(nil, nil, coins) chainID_1 := "test_chain" genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) - genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) - // Successfully register a chain - res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ - BlockchainGenesis{ - ChainID: "test_chain", - Genesis: string(genDocJSON_1), - }, + // Register a chain + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) + + // send coins to this addr on the other chain + destinationAddr := []byte("some address") + coinsBad := types.Coins{types.Coin{"mycoin", 200}} + coinsGood := types.Coins{types.Coin{"mycoin", 1}} + + // Try to send too many coins + packet := NewPacket("test_chain", "dst_chain", 0, CoinsPayload{ + Address: destinationAddr, + Coins: coinsBad, + }) + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet: packet, }})) - assert.True(res.IsOK(), res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_InsufficientFunds) - // Construct a Header - header := tm.Header{ - ChainID: "test_chain", - Height: 999, - ValidatorsHash: []byte("must_exist"), // TODO make optional - } - - // Construct a Commit that signs above header - blockHash := header.Hash() - blockID := tm.BlockID{Hash: blockHash} - commit := tm.Commit{ - BlockID: blockID, - Precommits: make([]*tm.Vote, len(privAccs_1)), - } - for i, privAcc := range privAccs_1 { - vote := &tm.Vote{ - ValidatorAddress: privAcc.Account.PubKey.Address(), - ValidatorIndex: i, - Height: 999, - Round: 0, - Type: tm.VoteTypePrecommit, - BlockID: tm.BlockID{Hash: blockHash}, - } - vote.Signature = privAcc.PrivKey.Sign( - tm.SignBytes("test_chain", vote), - ) - commit.Precommits[i] = vote - } - - // Update a chain with a broken commit - // Modify the first byte of the first signature - sig := commit.Precommits[0].Signature.(crypto.SignatureEd25519) - sig[0] += 1 - commit.Precommits[0].Signature = sig - res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ - Header: header, - Commit: commit, - }})) - assert.Equal(IBCCodeInvalidCommit, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() - -} - -func TestIBCPluginBadProof(t *testing.T) { - assert := assert.New(t) - - eyesClient := eyes.NewLocalClient("", 0) - store := types.NewKVCache(eyesClient) - store.SetLogging() // Log all activity - - ibcPlugin := New() - ctx := types.CallContext{ - CallerAddress: nil, - CallerAccount: nil, - Coins: types.Coins{}, - } - - chainID_1 := "test_chain" - genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) - genDocJSON_1 := wire.JSONBytesPretty(genDoc_1) - - // Successfully register a chain - res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ - BlockchainGenesis{ - ChainID: "test_chain", - Genesis: string(genDocJSON_1), - }, - }})) - assert.True(res.IsOK(), res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() - - // Create a new packet (for testing) - packet := Packet{ - SrcChainID: "test_chain", - DstChainID: "dst_chain", - Sequence: 0, - Type: "data", - Payload: []byte("hello world"), - } + // Send a small enough number of coins + packet = NewPacket("test_chain", "dst_chain", 0, CoinsPayload{ + Address: destinationAddr, + Coins: coinsGood, + }) res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ Packet: packet, }})) - assert.Equal(abci.CodeType_OK, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_OK) // Construct a Header that includes the above packet. store.Sync() resCommit := eyesClient.CommitSync() appHash := resCommit.Data - header := tm.Header{ - ChainID: "test_chain", - Height: 999, - AppHash: appHash, - ValidatorsHash: []byte("must_exist"), // TODO make optional - } + header := newHeader("test_chain", 999, appHash, []byte("must_exist")) // Construct a Commit that signs above header - blockHash := header.Hash() - blockID := tm.BlockID{Hash: blockHash} - commit := tm.Commit{ - BlockID: blockID, - Precommits: make([]*tm.Vote, len(privAccs_1)), - } - for i, privAcc := range privAccs_1 { - vote := &tm.Vote{ - ValidatorAddress: privAcc.Account.PubKey.Address(), - ValidatorIndex: i, - Height: 999, - Round: 0, - Type: tm.VoteTypePrecommit, - BlockID: tm.BlockID{Hash: blockHash}, - } - vote.Signature = privAcc.PrivKey.Sign( - tm.SignBytes("test_chain", vote), - ) - commit.Precommits[i] = vote - } + commit := constructCommit(privAccs_1, header) // Update a chain res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ Header: header, Commit: commit, }})) - assert.Equal(abci.CodeType_OK, res.Code, res.Log) - t.Log(">>", strings.Join(store.GetLogLines(), "\n")) - store.ClearLogLines() + assertAndLog(t, store, res, abci.CodeType_OK) // Get proof for the packet packetKey := toKey(_IBC, _EGRESS, @@ -379,7 +328,120 @@ func TestIBCPluginBadProof(t *testing.T) { Prove: true, }) assert.Nil(err) - var proof *merkle.IAVLProof + var proof *iavl.IAVLProof + err = wire.ReadBinaryBytes(resQuery.Proof, &proof) + assert.Nil(err) + + // Account should be empty before the tx + acc := types.GetAccount(store, destinationAddr) + assert.Nil(acc) + + // Post a packet + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketPostTx{ + FromChainID: "test_chain", + FromChainHeight: 999, + Packet: packet, + Proof: proof, + }})) + assertAndLog(t, store, res, abci.CodeType_OK) + + // Account should now have some coins + acc = types.GetAccount(store, destinationAddr) + assert.Equal(acc.Balance, coinsGood) +} + +func TestIBCPluginBadCommit(t *testing.T) { + require := require.New(t) + + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.NewCallContext(nil, nil, types.Coins{}) + + chainID_1 := "test_chain" + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) + + // Successfully register a chain + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) + + // Construct a Header + header := newHeader("test_chain", 999, nil, []byte("must_exist")) + + // Construct a Commit that signs above header + commit := constructCommit(privAccs_1, header) + + // Update a chain with a broken commit + // Modify the first byte of the first signature + sig := commit.Precommits[0].Signature.Unwrap().(crypto.SignatureEd25519) + sig[0] += 1 + commit.Precommits[0].Signature = sig.Wrap() + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ + Header: header, + Commit: commit, + }})) + assertAndLog(t, store, res, IBCCodeInvalidCommit) + +} + +func TestIBCPluginBadProof(t *testing.T) { + assert := assert.New(t) + require := require.New(t) + + eyesClient := eyes.NewLocalClient("", 0) + store := types.NewKVCache(eyesClient) + store.SetLogging() // Log all activity + + ibcPlugin := New() + ctx := types.NewCallContext(nil, nil, types.Coins{}) + + chainID_1 := "test_chain" + genDoc_1, privAccs_1 := genGenesisDoc(chainID_1, 4) + genDocJSON_1, err := json.Marshal(genDoc_1) + require.Nil(err) + + // Successfully register a chain + registerChain(t, ibcPlugin, store, ctx, "test_chain", string(genDocJSON_1)) + + // Create a new packet (for testing) + packet := NewPacket("test_chain", "dst_chain", 0, DataPayload([]byte("hello world"))) + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCPacketCreateTx{ + Packet: packet, + }})) + assertAndLog(t, store, res, abci.CodeType_OK) + + // Construct a Header that includes the above packet. + store.Sync() + resCommit := eyesClient.CommitSync() + appHash := resCommit.Data + header := newHeader("test_chain", 999, appHash, []byte("must_exist")) + + // Construct a Commit that signs above header + commit := constructCommit(privAccs_1, header) + + // Update a chain + res = ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCUpdateChainTx{ + Header: header, + Commit: commit, + }})) + assertAndLog(t, store, res, abci.CodeType_OK) + + // Get proof for the packet + packetKey := toKey(_IBC, _EGRESS, + packet.SrcChainID, + packet.DstChainID, + cmn.Fmt("%v", packet.Sequence), + ) + resQuery, err := eyesClient.QuerySync(abci.RequestQuery{ + Path: "/store", + Data: packetKey, + Prove: true, + }) + assert.Nil(err) + var proof *iavl.IAVLProof err = wire.ReadBinaryBytes(resQuery.Proof, &proof) assert.Nil(err) @@ -393,7 +455,58 @@ func TestIBCPluginBadProof(t *testing.T) { Packet: packet, Proof: proof, }})) - assert.Equal(IBCCodeInvalidProof, res.Code, res.Log) + assertAndLog(t, store, res, IBCCodeInvalidProof) +} + +//------------------------------------- +// utils + +func assertAndLog(t *testing.T, store *types.KVCache, res abci.Result, codeExpected abci.CodeType) { + assert := assert.New(t) + assert.Equal(codeExpected, res.Code, res.Log) t.Log(">>", strings.Join(store.GetLogLines(), "\n")) store.ClearLogLines() } + +func newHeader(chainID string, height int, appHash, valHash []byte) tm.Header { + return tm.Header{ + ChainID: chainID, + Height: height, + AppHash: appHash, + ValidatorsHash: valHash, + } +} + +func registerChain(t *testing.T, ibcPlugin *IBCPlugin, store *types.KVCache, ctx types.CallContext, chainID, genDoc string) { + res := ibcPlugin.RunTx(store, ctx, wire.BinaryBytes(struct{ IBCTx }{IBCRegisterChainTx{ + BlockchainGenesis{ + ChainID: chainID, + Genesis: genDoc, + }, + }})) + assertAndLog(t, store, res, abci.CodeType_OK) +} + +func constructCommit(privAccs []types.PrivAccount, header tm.Header) tm.Commit { + blockHash := header.Hash() + blockID := tm.BlockID{Hash: blockHash} + commit := tm.Commit{ + BlockID: blockID, + Precommits: make([]*tm.Vote, len(privAccs)), + } + for i, privAcc := range privAccs { + vote := &tm.Vote{ + ValidatorAddress: privAcc.Account.PubKey.Address(), + ValidatorIndex: i, + Height: 999, + Round: 0, + Type: tm.VoteTypePrecommit, + BlockID: tm.BlockID{Hash: blockHash}, + } + vote.Signature = privAcc.PrivKey.Sign( + tm.SignBytes("test_chain", vote), + ) + commit.Precommits[i] = vote + } + return commit +} diff --git a/scripts/print_txs.go b/scripts/print_txs.go index 689173a90..d31e70ffa 100644 --- a/scripts/print_txs.go +++ b/scripts/print_txs.go @@ -9,9 +9,9 @@ import ( "time" "github.com/gorilla/websocket" - cmn "github.com/tendermint/go-common" - "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-rpc/types" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/go-wire" _ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types ) diff --git a/state/execution.go b/state/execution.go index eb7f3c690..307c0d463 100644 --- a/state/execution.go +++ b/state/execution.go @@ -2,14 +2,15 @@ package state import ( abci "github.com/tendermint/abci/types" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/events" + + "github.com/tendermint/basecoin/plugins/ibc" "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - "github.com/tendermint/go-events" ) // If the tx is invalid, a TMSP error will be returned. func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc events.Fireable) abci.Result { - chainID := state.GetChainID() // Exec tx @@ -95,11 +96,11 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e signBytes := tx.SignBytes(chainID) res = validateInputAdvanced(inAcc, signBytes, tx.Input) if res.IsErr() { - log.Info(cmn.Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res)) + state.logger.Info(cmn.Fmt("validateInputAdvanced failed on %X: %v", tx.Input.Address, res)) return res.PrependLog("in validateInputAdvanced()") } if !tx.Input.Coins.IsGTE(types.Coins{tx.Fee}) { - log.Info(cmn.Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) + state.logger.Info(cmn.Fmt("Sender did not send enough to cover the fee %X", tx.Input.Address)) return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("input coins is %v, but fee is %v", tx.Input.Coins, types.Coins{tx.Fee})) } @@ -131,7 +132,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e res = plugin.RunTx(cache, ctx, tx.Data) if res.IsOK() { cache.CacheSync() - log.Info("Successful execution") + state.logger.Info("Successful execution") // Fire events /* if evc != nil { @@ -144,7 +145,7 @@ func ExecTx(state *State, pgz *types.Plugins, tx types.Tx, isCheckTx bool, evc e } */ } else { - log.Info("AppTx failed", "error", res) + state.logger.Info("AppTx failed", "error", res) // Just return the coins and return. inAccCopy.Balance = inAccCopy.Balance.Plus(coins) // But take the gas @@ -190,17 +191,23 @@ func getOrMakeOutputs(state types.AccountGetter, accounts map[string]*types.Acco } for _, out := range outs { + chain, outAddress, _ := out.ChainAndAddress() // already validated + if chain != nil { + // we dont need an account for the other chain. + // we'll just create an outgoing ibc packet + continue + } // Account shouldn't be duplicated - if _, ok := accounts[string(out.Address)]; ok { + if _, ok := accounts[string(outAddress)]; ok { return nil, abci.ErrBaseDuplicateAddress } - acc := state.GetAccount(out.Address) + acc := state.GetAccount(outAddress) // output account may be nil (new) if acc == nil { // zero value is valid, empty account acc = &types.Account{} } - accounts[string(out.Address)] = acc + accounts[string(outAddress)] = acc } return accounts, abci.OK } @@ -244,7 +251,7 @@ func validateInputAdvanced(acc *types.Account, signBytes []byte, in types.TxInpu return abci.ErrBaseInsufficientFunds.AppendLog(cmn.Fmt("balance is %v, tried to send %v", balance, in.Coins)) } // Check signatures - if !acc.PubKey.VerifyBytes(signBytes, in.Signature.Signature) { + if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { return abci.ErrBaseInvalidSignature.AppendLog(cmn.Fmt("SignBytes: %X", signBytes)) } return abci.OK @@ -282,15 +289,22 @@ func adjustByInputs(state types.AccountSetter, accounts map[string]*types.Accoun } } -func adjustByOutputs(state types.AccountSetter, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) { +func adjustByOutputs(state *State, accounts map[string]*types.Account, outs []types.TxOutput, isCheckTx bool) { for _, out := range outs { - acc := accounts[string(out.Address)] + destChain, outAddress, _ := out.ChainAndAddress() // already validated + if destChain != nil { + payload := ibc.CoinsPayload{outAddress, out.Coins} + ibc.SaveNewIBCPacket(state, state.GetChainID(), string(destChain), payload) + continue + } + + acc := accounts[string(outAddress)] if acc == nil { cmn.PanicSanity("adjustByOutputs() expects account in accounts") } acc.Balance = acc.Balance.Plus(out.Coins) if !isCheckTx { - state.SetAccount(out.Address, acc) + state.SetAccount(outAddress, acc) } } } diff --git a/state/execution_test.go b/state/execution_test.go index 249380b9f..b69e66c40 100644 --- a/state/execution_test.go +++ b/state/execution_test.go @@ -6,6 +6,9 @@ import ( "github.com/stretchr/testify/assert" abci "github.com/tendermint/abci/types" + "github.com/tendermint/tmlibs/log" + + "github.com/tendermint/basecoin/plugins/ibc" "github.com/tendermint/basecoin/types" ) @@ -32,11 +35,6 @@ func (et *execTest) signTx(tx *types.SendTx, accsIn ...types.PrivAccount) { types.SignTx(et.chainID, tx, accsIn...) } -// make tx from accsIn to et.accOut -func (et *execTest) getTx(seq int, accOut types.PrivAccount, accsIn ...types.PrivAccount) *types.SendTx { - return types.GetTx(seq, accOut, accsIn...) -} - // returns the final balance and expected balance for input and output accounts func (et *execTest) exec(tx *types.SendTx, checkTx bool) (res abci.Result, inGot, inExp, outGot, outExp types.Coins) { initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance @@ -63,6 +61,7 @@ func (et *execTest) reset() { et.store = types.NewMemKVStore() et.state = NewState(et.store) + et.state.SetLogger(log.TestingLogger()) et.state.SetChainID(et.chainID) // NOTE we dont run acc2State here @@ -83,19 +82,19 @@ func TestGetInputs(t *testing.T) { //test getInputs for registered, non-registered account et.reset() - txs := types.Accs2TxInputs(1, et.accIn) - acc, res = getInputs(et.state, txs) + inputs := types.Accs2TxInputs(1, et.accIn) + acc, res = getInputs(et.state, inputs) assert.True(res.IsErr(), "getInputs: expected error when using getInput with non-registered Input") et.acc2State(et.accIn) - acc, res = getInputs(et.state, txs) + acc, res = getInputs(et.state, inputs) assert.True(res.IsOK(), "getInputs: expected to getInput from registered Input") //test sending duplicate accounts et.reset() et.acc2State(et.accIn, et.accIn, et.accIn) - txs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn) - acc, res = getInputs(et.state, txs) + inputs = types.Accs2TxInputs(1, et.accIn, et.accIn, et.accIn) + acc, res = getInputs(et.state, inputs) assert.True(res.IsErr(), "getInputs: expected error when sending duplicate accounts") } @@ -110,24 +109,24 @@ func TestGetOrMakeOutputs(t *testing.T) { //test sending duplicate accounts et.reset() - txs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn) - _, res = getOrMakeOutputs(et.state, nil, txs) + outputs := types.Accs2TxOutputs(et.accIn, et.accIn, et.accIn) + _, res = getOrMakeOutputs(et.state, nil, outputs) assert.True(res.IsErr(), "getOrMakeOutputs: expected error when sending duplicate accounts") //test sending to existing/new account et.reset() - txs1 := types.Accs2TxOutputs(et.accIn) - txs2 := types.Accs2TxOutputs(et.accOut) + outputs1 := types.Accs2TxOutputs(et.accIn) + outputs2 := types.Accs2TxOutputs(et.accOut) et.acc2State(et.accIn) - _, res = getOrMakeOutputs(et.state, nil, txs1) + _, res = getOrMakeOutputs(et.state, nil, outputs1) assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to existing account") - mapRes2, res := getOrMakeOutputs(et.state, nil, txs2) + mapRes2, res := getOrMakeOutputs(et.state, nil, outputs2) assert.True(res.IsOK(), "getOrMakeOutputs: error when sending to new account") //test the map results - _, map2ok := mapRes2[string(txs2[0].Address)] + _, map2ok := mapRes2[string(outputs2[0].Address)] assert.True(map2ok, "getOrMakeOutputs: account output does not contain new account map item") } @@ -137,12 +136,12 @@ func TestValidateInputsBasic(t *testing.T) { et := newExecTest() //validate input basic - txs := types.Accs2TxInputs(1, et.accIn) - res := validateInputsBasic(txs) + inputs := types.Accs2TxInputs(1, et.accIn) + res := validateInputsBasic(inputs) assert.True(res.IsOK(), "validateInputsBasic: expected no error on good tx input. Error: %v", res.Error()) - txs[0].Coins[0].Amount = 0 - res = validateInputsBasic(txs) + inputs[0].Coins[0].Amount = 0 + res = validateInputsBasic(inputs) assert.True(res.IsErr(), "validateInputsBasic: expected error on bad tx input") } @@ -157,28 +156,28 @@ func TestValidateInputsAdvanced(t *testing.T) { accIn3 := types.MakeAcc("fooz") //validate inputs advanced - txs := et.getTx(1, et.accOut, accIn1, accIn2, accIn3) + tx := types.MakeSendTx(1, et.accOut, accIn1, accIn2, accIn3) et.acc2State(accIn1, accIn2, accIn3, et.accOut) - accMap, res := getInputs(et.state, txs.Inputs) + accMap, res := getInputs(et.state, tx.Inputs) assert.True(res.IsOK(), "validateInputsAdvanced: error retrieving accMap. Error: %v", res.Error()) - signBytes := txs.SignBytes(et.chainID) + signBytes := tx.SignBytes(et.chainID) //test bad case, unsigned - totalCoins, res := validateInputsAdvanced(accMap, signBytes, txs.Inputs) + totalCoins, res := validateInputsAdvanced(accMap, signBytes, tx.Inputs) assert.True(res.IsErr(), "validateInputsAdvanced: expected an error on an unsigned tx input") //test good case sgined - et.signTx(txs, accIn1, accIn2, accIn3, et.accOut) - totalCoins, res = validateInputsAdvanced(accMap, signBytes, txs.Inputs) + et.signTx(tx, accIn1, accIn2, accIn3, et.accOut) + totalCoins, res = validateInputsAdvanced(accMap, signBytes, tx.Inputs) assert.True(res.IsOK(), "validateInputsAdvanced: expected no error on good tx input. Error: %v", res.Error()) - txsTotalCoins := txs.Inputs[0].Coins. - Plus(txs.Inputs[1].Coins). - Plus(txs.Inputs[2].Coins) + txTotalCoins := tx.Inputs[0].Coins. + Plus(tx.Inputs[1].Coins). + Plus(tx.Inputs[2].Coins) - assert.True(totalCoins.IsEqual(txsTotalCoins), - "ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txsTotalCoins, totalCoins) + assert.True(totalCoins.IsEqual(txTotalCoins), + "ValidateInputsAdvanced: transaction total coins are not equal: got %v, expected %v", txTotalCoins, totalCoins) } func TestValidateInputAdvanced(t *testing.T) { @@ -186,31 +185,31 @@ func TestValidateInputAdvanced(t *testing.T) { et := newExecTest() //validate input advanced - txs := et.getTx(1, et.accOut, et.accIn) + tx := types.MakeSendTx(1, et.accOut, et.accIn) et.acc2State(et.accIn, et.accOut) - signBytes := txs.SignBytes(et.chainID) + signBytes := tx.SignBytes(et.chainID) //unsigned case - res := validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) + res := validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0]) assert.True(res.IsErr(), "validateInputAdvanced: expected error on tx input without signature") //good signed case - et.signTx(txs, et.accIn, et.accOut) - res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) + et.signTx(tx, et.accIn, et.accOut) + res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0]) assert.True(res.IsOK(), "validateInputAdvanced: expected no error on good tx input. Error: %v", res.Error()) //bad sequence case et.accIn.Sequence = 1 - et.signTx(txs, et.accIn, et.accOut) - res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) + et.signTx(tx, et.accIn, et.accOut) + res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0]) assert.Equal(abci.CodeType_BaseInvalidSequence, res.Code, "validateInputAdvanced: expected error on tx input with bad sequence") et.accIn.Sequence = 0 //restore sequence //bad balance case et.accIn.Balance = types.Coins{{"mycoin", 2}} - et.signTx(txs, et.accIn, et.accOut) - res = validateInputAdvanced(&et.accIn.Account, signBytes, txs.Inputs[0]) + et.signTx(tx, et.accIn, et.accOut) + res = validateInputAdvanced(&et.accIn.Account, signBytes, tx.Inputs[0]) assert.Equal(abci.CodeType_BaseInsufficientFunds, res.Code, "validateInputAdvanced: expected error on tx input with insufficient funds %v", et.accIn.Sequence) } @@ -220,12 +219,12 @@ func TestValidateOutputsAdvanced(t *testing.T) { et := newExecTest() //validateOutputsBasic - txs := types.Accs2TxOutputs(et.accIn) - res := validateOutputsBasic(txs) + tx := types.Accs2TxOutputs(et.accIn) + res := validateOutputsBasic(tx) assert.True(res.IsOK(), "validateOutputsBasic: expected no error on good tx output. Error: %v", res.Error()) - txs[0].Coins[0].Amount = 0 - res = validateOutputsBasic(txs) + tx[0].Coins[0].Amount = 0 + res = validateOutputsBasic(tx) assert.True(res.IsErr(), "validateInputBasic: expected error on bad tx output. Error: %v", res.Error()) } @@ -234,9 +233,9 @@ func TestSumOutput(t *testing.T) { et := newExecTest() //SumOutput - txs := types.Accs2TxOutputs(et.accIn, et.accOut) - total := sumOutputs(txs) - assert.True(total.IsEqual(txs[0].Coins.Plus(txs[1].Coins)), "sumOutputs: total coins are not equal") + tx := types.Accs2TxOutputs(et.accIn, et.accOut) + total := sumOutputs(tx) + assert.True(total.IsEqual(tx[0].Coins.Plus(tx[1].Coins)), "sumOutputs: total coins are not equal") } func TestAdjustBy(t *testing.T) { @@ -269,23 +268,23 @@ func TestAdjustBy(t *testing.T) { } -func TestExecTx(t *testing.T) { +func TestSendTx(t *testing.T) { assert := assert.New(t) et := newExecTest() //ExecTx - txs := et.getTx(1, et.accOut, et.accIn) + tx := types.MakeSendTx(1, et.accOut, et.accIn) et.acc2State(et.accIn) et.acc2State(et.accOut) - et.signTx(txs, et.accIn) + et.signTx(tx, et.accIn) //Bad Balance et.accIn.Balance = types.Coins{{"mycoin", 2}} et.acc2State(et.accIn) - res, _, _, _, _ := et.exec(txs, true) + res, _, _, _, _ := et.exec(tx, true) assert.True(res.IsErr(), "ExecTx/Bad CheckTx: Expected error return from ExecTx, returned: %v", res) - res, balIn, balInExp, balOut, balOutExp := et.exec(txs, false) + res, balIn, balInExp, balOut, balOutExp := et.exec(tx, false) assert.True(res.IsErr(), "ExecTx/Bad DeliverTx: Expected error return from ExecTx, returned: %v", res) assert.False(balIn.IsEqual(balInExp), "ExecTx/Bad DeliverTx: balance shouldn't be equal for accIn: got %v, expected: %v", balIn, balInExp) @@ -296,17 +295,59 @@ func TestExecTx(t *testing.T) { et.reset() et.acc2State(et.accIn) et.acc2State(et.accOut) - res, _, _, _, _ = et.exec(txs, true) + res, _, _, _, _ = et.exec(tx, true) assert.True(res.IsOK(), "ExecTx/Good CheckTx: Expected OK return from ExecTx, Error: %v", res) //Regular DeliverTx et.reset() et.acc2State(et.accIn) et.acc2State(et.accOut) - res, balIn, balInExp, balOut, balOutExp = et.exec(txs, false) + res, balIn, balInExp, balOut, balOutExp = et.exec(tx, false) assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res) assert.True(balIn.IsEqual(balInExp), "ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp) assert.True(balOut.IsEqual(balOutExp), "ExecTx/good DeliverTx: unexpected change in output balance, got: %v, expected: %v", balOut, balOutExp) } + +func TestSendTxIBC(t *testing.T) { + assert := assert.New(t) + et := newExecTest() + + //ExecTx + chainID2 := "otherchain" + tx := types.MakeSendTx(1, et.accOut, et.accIn) + dstAddress := tx.Outputs[0].Address + tx.Outputs[0].Address = []byte(chainID2 + "/" + string(tx.Outputs[0].Address)) + et.acc2State(et.accIn) + et.signTx(tx, et.accIn) + + //Regular DeliverTx + et.reset() + et.acc2State(et.accIn) + + initBalIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance + + res := ExecTx(et.state, nil, tx, false, nil) + + balIn := et.state.GetAccount(et.accIn.Account.PubKey.Address()).Balance + decrBalInExp := tx.Outputs[0].Coins.Plus(types.Coins{tx.Fee}) //expected decrease in balance In + balInExp := initBalIn.Minus(decrBalInExp) + + assert.True(res.IsOK(), "ExecTx/Good DeliverTx: Expected OK return from ExecTx, Error: %v", res) + assert.True(balIn.IsEqual(balInExp), + "ExecTx/good DeliverTx: unexpected change in input balance, got: %v, expected: %v", balIn, balInExp) + + packet, err := ibc.GetIBCPacket(et.state, et.chainID, chainID2, 0) + assert.Nil(err) + + assert.Equal(packet.SrcChainID, et.chainID) + assert.Equal(packet.DstChainID, chainID2) + assert.Equal(packet.Sequence, uint64(0)) + assert.Equal(packet.Type, "coin") + + coins, ok := packet.Payload.(ibc.CoinsPayload) + assert.True(ok) + assert.Equal(coins.Coins, tx.Outputs[0].Coins) + assert.EqualValues(coins.Address, dstAddress) +} diff --git a/state/log.go b/state/log.go deleted file mode 100644 index 5b102b570..000000000 --- a/state/log.go +++ /dev/null @@ -1,7 +0,0 @@ -package state - -import ( - "github.com/tendermint/go-logger" -) - -var log = logger.New("module", "state") diff --git a/state/state.go b/state/state.go index 68a7c3624..e9daf73b1 100644 --- a/state/state.go +++ b/state/state.go @@ -3,9 +3,8 @@ package state import ( abci "github.com/tendermint/abci/types" "github.com/tendermint/basecoin/types" - . "github.com/tendermint/go-common" - "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" + "github.com/tendermint/tmlibs/log" ) // CONTRACT: State should be quick to copy. @@ -15,6 +14,7 @@ type State struct { store types.KVStore readCache map[string][]byte // optional, for caching writes to store writeCache *types.KVCache // optional, for caching writes w/o writing to store + logger log.Logger } func NewState(store types.KVStore) *State { @@ -23,9 +23,14 @@ func NewState(store types.KVStore) *State { store: store, readCache: make(map[string][]byte), writeCache: nil, + logger: log.NewNopLogger(), } } +func (s *State) SetLogger(l log.Logger) { + s.logger = l +} + func (s *State) SetChainID(chainID string) { s.chainID = chainID s.store.Set([]byte("base/chain_id"), []byte(chainID)) @@ -57,11 +62,11 @@ func (s *State) Set(key []byte, value []byte) { } func (s *State) GetAccount(addr []byte) *types.Account { - return GetAccount(s, addr) + return types.GetAccount(s, addr) } func (s *State) SetAccount(addr []byte, acc *types.Account) { - SetAccount(s, addr, acc) + types.SetAccount(s, addr, acc) } func (s *State) CacheWrap() *State { @@ -71,6 +76,7 @@ func (s *State) CacheWrap() *State { store: cache, readCache: nil, writeCache: cache, + logger: s.logger, } } @@ -89,28 +95,3 @@ func (s *State) Commit() abci.Result { } } - -//---------------------------------------- - -func AccountKey(addr []byte) []byte { - return append([]byte("base/a/"), addr...) -} - -func GetAccount(store types.KVStore, addr []byte) *types.Account { - data := store.Get(AccountKey(addr)) - if len(data) == 0 { - return nil - } - var acc *types.Account - err := wire.ReadBinaryBytes(data, &acc) - if err != nil { - panic(Fmt("Error reading account %X error: %v", - data, err.Error())) - } - return acc -} - -func SetAccount(store types.KVStore, addr []byte, acc *types.Account) { - accBytes := wire.BinaryBytes(acc) - store.Set(AccountKey(addr), accBytes) -} diff --git a/state/state_test.go b/state/state_test.go index 1d35f2c4f..dae620027 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -6,6 +6,7 @@ import ( "github.com/tendermint/basecoin/types" eyes "github.com/tendermint/merkleeyes/client" + "github.com/tendermint/tmlibs/log" "github.com/stretchr/testify/assert" ) @@ -16,6 +17,7 @@ func TestState(t *testing.T) { //States and Stores for tests store := types.NewMemKVStore() state := NewState(store) + state.SetLogger(log.TestingLogger()) cache := state.CacheWrap() eyesCli := eyes.NewLocalClient("", 0) @@ -29,12 +31,14 @@ func TestState(t *testing.T) { reset := func() { store = types.NewMemKVStore() state = NewState(store) + state.SetLogger(log.TestingLogger()) cache = state.CacheWrap() } //set the state to using the eyesCli instead of MemKVStore useEyesCli := func() { state = NewState(eyesCli) + state.SetLogger(log.TestingLogger()) cache = state.CacheWrap() } diff --git a/tests/tendermint/main.go b/tests/tendermint/main.go index 73ace4ef8..04a39f9f6 100644 --- a/tests/tendermint/main.go +++ b/tests/tendermint/main.go @@ -6,12 +6,11 @@ import ( "github.com/gorilla/websocket" "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - crypto "github.com/tendermint/go-crypto" - rpcclient "github.com/tendermint/go-rpc/client" - "github.com/tendermint/go-rpc/types" wire "github.com/tendermint/go-wire" _ "github.com/tendermint/tendermint/rpc/core/types" // Register RPCResponse > Result types + "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/rpc/lib/types" + cmn "github.com/tendermint/tmlibs/common" ) func main() { @@ -67,16 +66,18 @@ func main() { // Sign request signBytes := tx.SignBytes(chainID) sig := root.Sign(signBytes) - tx.Inputs[0].Signature = crypto.SignatureS{sig} + tx.Inputs[0].Signature = sig //fmt.Println("tx:", tx) // Write request txBytes := wire.BinaryBytes(struct{ types.Tx }{tx}) - request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) - //request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) + request, err := rpctypes.MapToRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) + if err != nil { + cmn.Exit("cannot encode request: " + err.Error()) + } reqBytes := wire.JSONBytes(request) //fmt.Print(".") - err := ws.WriteMessage(websocket.TextMessage, reqBytes) + err = ws.WriteMessage(websocket.TextMessage, reqBytes) if err != nil { cmn.Exit("writing websocket request: " + err.Error()) } @@ -118,15 +119,18 @@ func main() { // Sign request signBytes := tx.SignBytes(chainID) sig := privAccountA.Sign(signBytes) - tx.Inputs[0].Signature = crypto.SignatureS{sig} + tx.Inputs[0].Signature = sig //fmt.Println("tx:", tx) // Write request txBytes := wire.BinaryBytes(struct{ types.Tx }{tx}) - request := rpctypes.NewRPCRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) + request, err := rpctypes.MapToRequest("fakeid", "broadcast_tx_sync", map[string]interface{}{"tx": txBytes}) + if err != nil { + cmn.Exit("cannot encode request: " + err.Error()) + } reqBytes := wire.JSONBytes(request) //fmt.Print(".") - err := ws.WriteMessage(websocket.TextMessage, reqBytes) + err = ws.WriteMessage(websocket.TextMessage, reqBytes) if err != nil { cmn.Exit("writing websocket request: " + err.Error()) } diff --git a/tests/tmsp/tmsp_test.go b/tests/tmsp/tmsp_test.go index 86b4bfbf2..19b32341b 100644 --- a/tests/tmsp/tmsp_test.go +++ b/tests/tmsp/tmsp_test.go @@ -8,16 +8,17 @@ import ( "github.com/stretchr/testify/require" "github.com/tendermint/basecoin/app" "github.com/tendermint/basecoin/types" - cmn "github.com/tendermint/go-common" - crypto "github.com/tendermint/go-crypto" - "github.com/tendermint/go-wire" + wire "github.com/tendermint/go-wire" eyescli "github.com/tendermint/merkleeyes/client" + cmn "github.com/tendermint/tmlibs/common" + "github.com/tendermint/tmlibs/log" ) func TestSendTx(t *testing.T) { eyesCli := eyescli.NewLocalClient("", 0) chainID := "test_chain_id" bcApp := app.NewBasecoin(eyesCli) + bcApp.SetLogger(log.TestingLogger().With("module", "app")) bcApp.SetOption("base/chain_id", chainID) // t.Log(bcApp.Info()) @@ -50,7 +51,7 @@ func TestSendTx(t *testing.T) { signBytes := tx.SignBytes(chainID) // t.Log("Sign bytes: %X\n", signBytes) sig := test1PrivAcc.Sign(signBytes) - tx.Inputs[0].Signature = crypto.SignatureS{sig} + tx.Inputs[0].Signature = sig // t.Log("Signed TX bytes: %X\n", wire.BinaryBytes(types.TxS{tx})) // Write request @@ -102,7 +103,7 @@ func TestSequence(t *testing.T) { // Sign request signBytes := tx.SignBytes(chainID) sig := test1PrivAcc.Sign(signBytes) - tx.Inputs[0].Signature = crypto.SignatureS{sig} + tx.Inputs[0].Signature = sig // t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address) // Write request @@ -146,7 +147,7 @@ func TestSequence(t *testing.T) { // Sign request signBytes := tx.SignBytes(chainID) sig := privAccountA.Sign(signBytes) - tx.Inputs[0].Signature = crypto.SignatureS{sig} + tx.Inputs[0].Signature = sig // t.Log("ADDR: %X -> %X\n", tx.Inputs[0].Address, tx.Outputs[0].Address) // Write request diff --git a/types/account.go b/types/account.go index b3478fce0..ec4154fc4 100644 --- a/types/account.go +++ b/types/account.go @@ -4,12 +4,13 @@ import ( "fmt" "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" ) type Account struct { - PubKey crypto.PubKeyS `json:"pub_key"` // May be nil, if not known. - Sequence int `json:"sequence"` - Balance Coins `json:"coins"` + PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. + Sequence int `json:"sequence"` + Balance Coins `json:"coins"` } func (acc *Account) Copy() *Account { @@ -31,7 +32,7 @@ func (acc *Account) String() string { //---------------------------------------- type PrivAccount struct { - crypto.PrivKeyS + crypto.PrivKey Account } @@ -49,3 +50,26 @@ type AccountGetterSetter interface { GetAccount(addr []byte) *Account SetAccount(addr []byte, acc *Account) } + +func AccountKey(addr []byte) []byte { + return append([]byte("base/a/"), addr...) +} + +func GetAccount(store KVStore, addr []byte) *Account { + data := store.Get(AccountKey(addr)) + if len(data) == 0 { + return nil + } + var acc *Account + err := wire.ReadBinaryBytes(data, &acc) + if err != nil { + panic(fmt.Sprintf("Error reading account %X error: %v", + data, err.Error())) + } + return acc +} + +func SetAccount(store KVStore, addr []byte, acc *Account) { + accBytes := wire.BinaryBytes(acc) + store.Set(AccountKey(addr), accBytes) +} diff --git a/types/coin.go b/types/coin.go index e34f7d1bd..7460a8e8a 100644 --- a/types/coin.go +++ b/types/coin.go @@ -3,8 +3,11 @@ package types import ( "fmt" "regexp" + "sort" "strconv" "strings" + + "github.com/pkg/errors" ) type Coin struct { @@ -17,22 +20,26 @@ func (coin Coin) String() string { } //regex codes for extracting coins from string -var reDenom = regexp.MustCompile("([^\\d\\W]+)") +var reDenom = regexp.MustCompile("") var reAmt = regexp.MustCompile("(\\d+)") -func ParseCoin(str string) (Coin, error) { +var reCoin = regexp.MustCompile("^([[:digit:]]+)[[:space:]]*([[:alpha:]]+)$") +func ParseCoin(str string) (Coin, error) { var coin Coin - if len(str) > 0 { - amt, err := strconv.Atoi(reAmt.FindString(str)) - if err != nil { - return coin, err - } - denom := reDenom.FindString(str) - coin = Coin{denom, int64(amt)} + matches := reCoin.FindStringSubmatch(strings.TrimSpace(str)) + if matches == nil { + return coin, errors.Errorf("%s is invalid coin definition", str) } + // parse the amount (should always parse properly) + amt, err := strconv.Atoi(matches[1]) + if err != nil { + return coin, err + } + + coin = Coin{matches[2], int64(amt)} return coin, nil } @@ -53,18 +60,26 @@ func (coins Coins) String() string { } func ParseCoins(str string) (Coins, error) { + // empty string is empty list... + if len(str) == 0 { + return nil, nil + } split := strings.Split(str, ",") - var coins []Coin + var coins Coins for _, el := range split { - if len(el) > 0 { - coin, err := ParseCoin(el) - if err != nil { - return coins, err - } - coins = append(coins, coin) + coin, err := ParseCoin(el) + if err != nil { + return coins, err } + coins = append(coins, coin) + } + + // ensure they are in proper order, to avoid random failures later + coins.Sort() + if !coins.IsValid() { + return nil, errors.Errorf("ParseCoins invalid: %#v", coins) } return coins, nil @@ -195,3 +210,10 @@ func (coins Coins) IsNonnegative() bool { } return true } + +/*** Implement Sort interface ***/ + +func (c Coins) Len() int { return len(c) } +func (c Coins) Less(i, j int) bool { return c[i].Denom < c[j].Denom } +func (c Coins) Swap(i, j int) { c[i], c[j] = c[j], c[i] } +func (c Coins) Sort() { sort.Sort(c) } diff --git a/types/coin_test.go b/types/coin_test.go index 8cbc708a4..bf651c50d 100644 --- a/types/coin_test.go +++ b/types/coin_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func TestCoins(t *testing.T) { @@ -56,30 +55,85 @@ func TestCoins(t *testing.T) { //Test the parse coin and parse coins functionality func TestParse(t *testing.T) { - assert, require := assert.New(t), require.New(t) + assert := assert.New(t) - makeCoin := func(str string) Coin { - coin, err := ParseCoin(str) - require.Nil(err) - return coin + cases := []struct { + input string + valid bool // if false, we expect an error on parse + expected Coins // if valid is true, make sure this is returned + }{ + {"", true, nil}, + {"1foo", true, Coins{{"foo", 1}}}, + {"10bar", true, Coins{{"bar", 10}}}, + {"99bar,1foo", true, Coins{{"bar", 99}, {"foo", 1}}}, + {"98 bar , 1 foo ", true, Coins{{"bar", 98}, {"foo", 1}}}, + {" 55\t \t bling\n", true, Coins{{"bling", 55}}}, + {"2foo, 97 bar", true, Coins{{"bar", 97}, {"foo", 2}}}, + {"5 mycoin,", false, nil}, // no empty coins in a list + {"2 3foo, 97 bar", false, nil}, // 3foo is invalid coin name + {"11me coin, 12you coin", false, nil}, // no spaces in coin names + {"1.2btc", false, nil}, // amount must be integer + {"5foo-bar", false, nil}, // once more, only letters in coin name } - makeCoins := func(str string) Coins { - coin, err := ParseCoins(str) - require.Nil(err) - return coin + for _, tc := range cases { + res, err := ParseCoins(tc.input) + if !tc.valid { + assert.NotNil(err, "%s: %#v", tc.input, res) + } else if assert.Nil(err, "%s: %+v", tc.input, err) { + assert.Equal(tc.expected, res) + } } - //testing ParseCoin Function - assert.Equal(Coin{}, makeCoin(""), "ParseCoin makes bad empty coin") - assert.Equal(Coin{"fooCoin", 1}, makeCoin("1fooCoin"), "ParseCoin makes bad coins") - assert.Equal(Coin{"barCoin", 10}, makeCoin("10 barCoin"), "ParseCoin makes bad coins") - - //testing ParseCoins Function - assert.True(Coins{{"fooCoin", 1}}.IsEqual(makeCoins("1fooCoin")), - "ParseCoins doesn't parse a single coin") - assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99barCoin,1fooCoin")), - "ParseCoins doesn't properly parse two coins") - assert.True(Coins{{"barCoin", 99}, {"fooCoin", 1}}.IsEqual(makeCoins("99 barCoin, 1 fooCoin")), - "ParseCoins doesn't properly parse two coins which use spaces") +} + +func TestSortCoins(t *testing.T) { + assert := assert.New(t) + + good := Coins{ + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + Coin{"TREE", 1}, + } + empty := Coins{ + Coin{"GOLD", 0}, + } + badSort1 := Coins{ + Coin{"TREE", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + badSort2 := Coins{ // both are after the first one, but the second and third are in the wrong order + Coin{"GAS", 1}, + Coin{"TREE", 1}, + Coin{"MINERAL", 1}, + } + badAmt := Coins{ + Coin{"GAS", 1}, + Coin{"TREE", 0}, + Coin{"MINERAL", 1}, + } + dup := Coins{ + Coin{"GAS", 1}, + Coin{"GAS", 1}, + Coin{"MINERAL", 1}, + } + + cases := []struct { + coins Coins + before, after bool // valid before/after sort + }{ + {good, true, true}, + {empty, false, false}, + {badSort1, false, true}, + {badSort2, false, true}, + {badAmt, false, false}, + {dup, false, false}, + } + + for _, tc := range cases { + assert.Equal(tc.before, tc.coins.IsValid()) + tc.coins.Sort() + assert.Equal(tc.after, tc.coins.IsValid()) + } } diff --git a/types/kvstore.go b/types/kvstore.go index 15088bdfb..a795d7dd1 100644 --- a/types/kvstore.go +++ b/types/kvstore.go @@ -4,7 +4,7 @@ import ( "container/list" "fmt" - . "github.com/tendermint/go-common" + . "github.com/tendermint/tmlibs/common" ) type KVStore interface { diff --git a/types/test_helpers.go b/types/test_helpers.go index 81e639ee1..2b7be27a9 100644 --- a/types/test_helpers.go +++ b/types/test_helpers.go @@ -3,18 +3,19 @@ package types // Helper functions for testing import ( - cmn "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" + cmn "github.com/tendermint/tmlibs/common" ) // Creates a PrivAccount from secret. // The amount is not set. func PrivAccountFromSecret(secret string) PrivAccount { - privKey := crypto.GenPrivKeyEd25519FromSecret([]byte(secret)) + privKey := + crypto.GenPrivKeyEd25519FromSecret([]byte(secret)).Wrap() privAccount := PrivAccount{ - PrivKeyS: crypto.PrivKeyS{privKey}, + PrivKey: privKey, Account: Account{ - PubKey: crypto.PubKeyS{privKey.PubKey()}, + PubKey: privKey.PubKey(), }, } return privAccount @@ -30,10 +31,10 @@ func RandAccounts(num int, minAmount int64, maxAmount int64) []PrivAccount { balance += cmn.RandInt64() % (maxAmount - minAmount) } - privKey := crypto.GenPrivKeyEd25519() - pubKey := crypto.PubKeyS{privKey.PubKey()} + privKey := crypto.GenPrivKeyEd25519().Wrap() + pubKey := privKey.PubKey() privAccs[i] = PrivAccount{ - PrivKeyS: crypto.PrivKeyS{privKey}, + PrivKey: privKey, Account: Account{ PubKey: pubKey, Balance: Coins{Coin{"", balance}}, @@ -85,20 +86,20 @@ func Accs2TxOutputs(accs ...PrivAccount) []TxOutput { return txs } -func GetTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx { - txs := &SendTx{ +func MakeSendTx(seq int, accOut PrivAccount, accsIn ...PrivAccount) *SendTx { + tx := &SendTx{ Gas: 0, Fee: Coin{"mycoin", 1}, Inputs: Accs2TxInputs(seq, accsIn...), Outputs: Accs2TxOutputs(accOut), } - return txs + return tx } func SignTx(chainID string, tx *SendTx, accs ...PrivAccount) { signBytes := tx.SignBytes(chainID) for i, _ := range tx.Inputs { - tx.Inputs[i].Signature = crypto.SignatureS{accs[i].Sign(signBytes)} + tx.Inputs[i].Signature = accs[i].Sign(signBytes) } } diff --git a/types/tx.go b/types/tx.go index d4925bbb6..a54d7caf0 100644 --- a/types/tx.go +++ b/types/tx.go @@ -5,10 +5,10 @@ import ( "encoding/json" abci "github.com/tendermint/abci/types" - . "github.com/tendermint/go-common" "github.com/tendermint/go-crypto" - "github.com/tendermint/go-data" "github.com/tendermint/go-wire" + "github.com/tendermint/go-wire/data" + . "github.com/tendermint/tmlibs/common" ) /* @@ -37,7 +37,7 @@ func (_ *AppTx) AssertIsTx() {} var txMapper data.Mapper -// register both private key types with go-data (and thus go-wire) +// register both private key types with go-wire/data (and thus go-wire) func init() { txMapper = data.NewMapper(TxS{}). RegisterImplementation(&SendTx{}, TxNameSend, TxTypeSend). @@ -46,7 +46,7 @@ func init() { // TxS add json serialization to Tx type TxS struct { - Tx + Tx `json:"unwrap"` } func (p TxS) MarshalJSON() ([]byte, error) { @@ -64,11 +64,11 @@ func (p *TxS) UnmarshalJSON(data []byte) (err error) { //----------------------------------------------------------------------------- type TxInput struct { - Address data.Bytes `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput - Signature crypto.SignatureS `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKeyS `json:"pub_key"` // Is present iff Sequence == 0 + Address data.Bytes `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx + PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 } func (txIn TxInput) ValidateBasic() abci.Result { @@ -104,13 +104,7 @@ func NewTxInput(pubKey crypto.PubKey, coins Coins, sequence int) TxInput { Sequence: sequence, } if sequence == 1 { - // safely wrap if needed - // TODO: extract this as utility function? - ps, ok := pubKey.(crypto.PubKeyS) - if !ok { - ps = crypto.PubKeyS{pubKey} - } - input.PubKey = ps + input.PubKey = pubKey } return input } @@ -122,10 +116,33 @@ type TxOutput struct { Coins Coins `json:"coins"` // } -func (txOut TxOutput) ValidateBasic() abci.Result { - if len(txOut.Address) != 20 { - return abci.ErrBaseInvalidOutput.AppendLog("Invalid address length") +// An output destined for another chain may be formatted as `chainID/address`. +// ChainAndAddress returns the chainID prefix and the address. +// If there is no chainID prefix, the first returned value is nil. +func (txOut TxOutput) ChainAndAddress() ([]byte, []byte, abci.Result) { + var chainPrefix []byte + address := txOut.Address + if len(address) > 20 { + spl := bytes.Split(address, []byte("/")) + if len(spl) < 2 { + return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address format") + } + chainPrefix = spl[0] + address = bytes.Join(spl[1:], nil) } + + if len(address) != 20 { + return nil, nil, abci.ErrBaseInvalidOutput.AppendLog("Invalid address length") + } + return chainPrefix, address, abci.OK +} + +func (txOut TxOutput) ValidateBasic() abci.Result { + _, _, r := txOut.ChainAndAddress() + if r.IsErr() { + return r + } + if !txOut.Coins.IsValid() { return abci.ErrBaseInvalidOutput.AppendLog(Fmt("Invalid coins %v", txOut.Coins)) } @@ -151,25 +168,21 @@ type SendTx struct { func (tx *SendTx) SignBytes(chainID string) []byte { signBytes := wire.BinaryBytes(chainID) sigz := make([]crypto.Signature, len(tx.Inputs)) - for i, input := range tx.Inputs { - sigz[i] = input.Signature.Signature - tx.Inputs[i].Signature.Signature = nil + for i := range tx.Inputs { + sigz[i] = tx.Inputs[i].Signature + tx.Inputs[i].Signature = crypto.Signature{} } signBytes = append(signBytes, wire.BinaryBytes(tx)...) for i := range tx.Inputs { - tx.Inputs[i].Signature.Signature = sigz[i] + tx.Inputs[i].Signature = sigz[i] } return signBytes } func (tx *SendTx) SetSignature(addr []byte, sig crypto.Signature) bool { - sigs, ok := sig.(crypto.SignatureS) - if !ok { - sigs = crypto.SignatureS{sig} - } for i, input := range tx.Inputs { if bytes.Equal(input.Address, addr) { - tx.Inputs[i].Signature = sigs + tx.Inputs[i].Signature = sig return true } } @@ -193,18 +206,14 @@ type AppTx struct { func (tx *AppTx) SignBytes(chainID string) []byte { signBytes := wire.BinaryBytes(chainID) sig := tx.Input.Signature - tx.Input.Signature.Signature = nil + tx.Input.Signature = crypto.Signature{} signBytes = append(signBytes, wire.BinaryBytes(tx)...) tx.Input.Signature = sig return signBytes } func (tx *AppTx) SetSignature(sig crypto.Signature) bool { - sigs, ok := sig.(crypto.SignatureS) - if !ok { - sigs = crypto.SignatureS{sig} - } - tx.Input.Signature = sigs + tx.Input.Signature = sig return true } diff --git a/types/tx_test.go b/types/tx_test.go index 71033cc9e..79ce9b90a 100644 --- a/types/tx_test.go +++ b/types/tx_test.go @@ -6,8 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - crypto "github.com/tendermint/go-crypto" - data "github.com/tendermint/go-data" + data "github.com/tendermint/go-wire/data" ) var chainID string = "test_chain" @@ -109,7 +108,7 @@ func TestSendTxJSON(t *testing.T) { sig := test1PrivAcc.Sign(signBytes) // we handle both raw sig and wrapped sig the same tx.SetSignature(test1PrivAcc.PubKey.Address(), sig) - tx2.SetSignature(test1PrivAcc.PubKey.Address(), crypto.SignatureS{sig}) + tx2.SetSignature(test1PrivAcc.PubKey.Address(), sig) assert.Equal(tx, tx2) // let's marshal / unmarshal this with signature diff --git a/version/version.go b/version/version.go index 382282305..07d89dbe9 100644 --- a/version/version.go +++ b/version/version.go @@ -1,7 +1,7 @@ package version const Maj = "0" -const Min = "4" -const Fix = "1" +const Min = "5" +const Fix = "0" -const Version = "0.4.1" +const Version = "0.5.0"