diff --git a/.gitignore b/.gitignore index 100b0cae2fb..dc0a2ef9e55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.swp vendor merkleeyes.db +build +shunit2 +docs/guide/*.sh + diff --git a/CHANGELOG.md b/CHANGELOG.md index d6205a7e94b..fdd0e7a5840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,49 @@ # Changelog +## 0.6.0 (June 22, 2017) + +Make the basecli command the only way to use client-side, to enforce best +security practices. Lots of enhancements to get it up to production quality. + +BREAKING CHANGES: +- ./cmd/commands -> ./cmd/basecoin/commands +- basecli + - `basecli proof state get` -> `basecli query key` + - `basecli proof tx get` -> `basecli query tx` + - `basecli proof state get --app=account` -> `basecli query account` + - use `--chain-id` not `--chainid` for consistency + - update to use `--trace` not `--debug` for stack traces on errors + - complete overhaul on how tx and query subcommands are added. (see counter or trackomatron for examples) + - no longer supports counter app (see new countercli) +- basecoin + - `basecoin init` takes an argument, an address to allocate funds to in the genesis + - removed key2.json + - removed all client side functionality from it (use basecli now for proofs) + - no tx subcommand + - no query subcommand + - no account (query) subcommand + - a few other random ones... + - enhanced relay subcommand + - relay start did what relay used to do + - relay init registers both chains on one another (to set it up so relay start just works) +- docs + - removed `example-plugin`, put `counter` inside `docs/guide` +- app + - Implements ABCI handshake by proxying merkleeyes.Info() + +ENHANCEMENTS: +- `basecoin init` support `--chain-id` +- intergrates tendermint 0.10.0 (not the rc-2, but the real thing) +- commands return error code (1) on failure for easier script testing +- add `reset_all` to basecli, and never delete keys on `init` +- new shutil based unit tests, with better coverage of the cli actions +- just `make fresh` when things are getting stale ;) + +BUG FIXES: +- app: no longer panics on missing app_options in genesis (thanks, anton) +- docs: updated all docs... again +- ibc: fix panic on getting BlockID from commit without 100% precommits (still a TODO) + ## 0.5.2 (June 2, 2017) BUG FIXES: diff --git a/Makefile b/Makefile index 7fe0004aa88..ea93496b7c4 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,56 @@ -all: test install +GOTOOLS = github.com/mitchellh/gox \ + github.com/Masterminds/glide -NOVENDOR = go list ./... | grep -v /vendor/ +all: get_vendor_deps install test build: go build ./cmd/... install: go install ./cmd/... + go install ./docs/guide/counter/cmd/... dist: - @ sudo bash scripts/dist.sh - @ bash scripts/publish.sh + @bash scripts/dist.sh + @bash scripts/publish.sh -test: - go test `${NOVENDOR}` - #go run tests/tendermint/*.go +test: test_unit test_cli -get_deps: - go get -d ./... +test_unit: + go test `glide novendor` + #go run tests/tendermint/*.go -update_deps: - go get -d -u ./... +test_cli: tests/cli/shunit2 + # sudo apt-get install jq + @./tests/cli/basictx.sh + @./tests/cli/counter.sh + @./tests/cli/restart.sh + @./tests/cli/ibc.sh -get_vendor_deps: - go get github.com/Masterminds/glide +get_vendor_deps: tools glide install build-docker: - docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w "/go/src/github.com/tendermint/basecoin" -e "CGO_ENABLED=0" golang:alpine go build ./cmd/basecoin + docker run -it --rm -v "$(PWD):/go/src/github.com/tendermint/basecoin" -w \ + "/go/src/github.com/tendermint/basecoin" -e "CGO_ENABLED=0" golang:alpine go build ./cmd/basecoin docker build -t "tendermint/basecoin" . +tests/cli/shunit2: + wget "https://raw.githubusercontent.com/kward/shunit2/master/source/2.1/src/shunit2" \ + -q -O tests/cli/shunit2 + +tools: + @go get $(GOTOOLS) + clean: - @rm -f ./basecoin + # maybe cleaning up cache and vendor is overkill, but sometimes + # you don't get the most recent versions with lots of branches, changes, rebases... + @rm -rf ~/.glide/cache/src/https-github.com-tendermint-* + @rm -rf ./vendor + @rm -f $GOPATH/bin/{basecoin,basecli,counter,countercli} + +# when your repo is getting a little stale... just make fresh +fresh: clean get_vendor_deps install + @if [[ `git status -s` ]]; then echo; echo "Warning: uncommited changes"; git status -s; fi -.PHONY: all build install test get_deps update_deps get_vendor_deps build-docker clean +.PHONY: all build install test test_cli test_unit get_vendor_deps build-docker clean fresh diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 00000000000..8c765be2d24 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,33 @@ +# Release Process + +Basecoin is the heart of most demo apps and the testnets, but the last few releases have been a little chaotic. In order to guarantee a higher, production-quality release in the future, we will work on a release process to check before the push to master. This is a work-in-progress and should be trialed on the 0.6.x patches, and used for the 0.7.0 release. + +This is a rough-guide. Please add comments here, let's try it out for 0.6.1 and see what is annoying and useless, and what is missing and useful. + +## Preparation + +* Clarify scope of release +* Create issues +* Write code +* Update CHANGELOG +* Create PR for release +* Update version + +## QA + +Once we have a PR for the release and think it is ready, we should test it out internally: + +* Code review (in addition to individual code reviews on the merged issue) +* Manual run-through of tutorials (and feedback on bad UX) +* Deployment of a private testnet, multiple users test out manually (feedback on bugs, or annoying UX) +* Test out upgrading existing testnet from last version, document or add tools for easier upgrade. +* If outstanding issues here, fix the issues, and repeat. + +## Release + +Once QA passes, we need to orchestrate the release. + +* Merge to master +* Set all glide dependencies to proper master versions of repos +* Push code with new version tag +* Upgrade our public-facing testnets with the latest versions diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 00000000000..9e344eb62f2 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,66 @@ +# Roadmap for future basecoin development + +Warning: there are current plans, they may change based on other developments, needs. The further in the future, the less clear, like all plans. + +## 0.6.x - Testnet and Light Client (late June 2017) + +The current release cycle is making sure the server is usable for deploying testnets (easy config, safe restarts, moving nodes). Also that we have a useable light client that does full cryptographic prooofs without syncing the entire block headers. See the [changelog](CHANGELOG.md). + +Patch release here involve improving the usability of the cli tools, adding subcommands, more flags, helper functions, shell integrations, etc. Please add issues if you find the client tool difficult to use, or deployment troublesome. + +## 0.7.x - Towards a modular framework (late July 2017) + +**Breaking changes** + +* Renaming likely: this release may well lead to a [renaming of the repository](https://github.com/tendermint/basecoin/issues/119) to emphasize that it is a generalized framework. `basecoin` and `basecli` executables will remain with generally unchanged usage. +* This will also provide a tx structure that is very different than the current one, and a non-trivial upgrade of running chains. + +The next release cycle involves a big upgrade to the core, especially how one can write modules (aka plugins) as well as configure a basecoin-based executable. The main goal is to leave us with basecoin as a single executable with a similar API, but create a new module/middleware system with a number of standard modules provided (and easy addition of third party modules), so developers can quickly mix-and-match pieces and add custom business logic for there chain. + +The main goal here is to migrate from a basecoin with plugins for extra enhancements, to a proper app development framework, of which basecoin is one example app that can quickly be built. + +Some ideas: + +* Flexible fee/gas system (good for both public and private blockchains) +* Flexible authentication systems (with multi-sig support) +* Basic role permission system +* Abstract IBC to support other transactions from various modules (not just sendtx) + +This will be done in conjunction with some sample apps also building on this framework, where other logic is interesting and money transfers is not the central goal, like [trackomatron](https://github.com/tendermint/trackomatron) + +## Next steps + +The following are three planned steps, the order of which may change. At least one or two of these will most likely occur before any other developments. Clearly, any other feature that are urgent for cosmos can jump the list in priority, but all these pieces are part of the cosmos roadmap, especially the first two. + +### 0.8.x??? - Local client API for UI + +Beyond the CLI, we want to add more interfaces for easily building a UI on top of the basecoin client. One clear example is a local REST API, so you can easily integrate with an electron app, or a chrome app, just as if you wrote a normal Single-Page Application, but connecting to a local proxy to do full crypto-graphic proofs. + +Another **possible** development is providing an SDK, which we can compile with [go-mobile](https://github.com/golang/go/wiki/Mobile) for both Android and iOS to support secure mobile applications. Progress on this front is contingent on participation of an experienced mobile developer. + +Further, when the planned enhancements to real-time events happen in tendermint core, we should expose that via a simple subscriber/listener model in these local APIs. + +### 0.9.x??? - Proof of Stake and Voting Modules + +We should integrate developments on a [proof-of-stake module](https://github.com/tendermint/basecoin-stake) (currently a work-in-progress) and basic voting modules (currently planned) into properly supported for modules. These would provide the basis for dynamic validator set changes with bondign periods, and the basis for making governance decisions (eg. voting to change the block reward). + +At this point we would have to give full support to these plugins, and third-party devs can build on them to add more complex delegation or governance logic. + +### 0.10.x??? - Database enhancements + +Depending on developments with merkleeyes, we would like to increase the expressiveness of the storage layer while maintaining provability of all queries. We would also add a number of new primatives to the key-value store, to allow some general data-structures. + +Also, full support for historical queries and performance optimizations of the storage later. But this all depends on supporting developments of another repo, so timing here is unclear. Here are some example ideas: + +Merkle proofs: + +* **Proof of key-value**: only current proof +* **Proof of missing key**: prove there is no data for that key +* **Proof of range**: one proof for all key-values in a range of keys +* **Proof of highest/lowest in range**: just get one key, for example, prove validator hash with highest height <= H + +Data structures: + +* **Queues**: provable push-pop operations, split over multiple keys, so it can scale to 1000s of entries without deserializing them all every time. +* **Priority Queues**: as above, but ordered by priority instead of FIFO. +* **Secondary Indexes**: add support for secondary indexes with proofs. So, I can not only prove my balance, but for example, the list of all accouns with a balance of > 1000000 atoms. These indexes would have to be created by the application and stored extra in the database, but if you have a common query that you want proofs/trust, it can be very useful. diff --git a/app/app.go b/app/app.go index 27a3b9c98ac..6f54782ff7a 100644 --- a/app/app.go +++ b/app/app.go @@ -8,7 +8,7 @@ import ( abci "github.com/tendermint/abci/types" wire "github.com/tendermint/go-wire" eyes "github.com/tendermint/merkleeyes/client" - . "github.com/tendermint/tmlibs/common" + cmn "github.com/tendermint/tmlibs/common" "github.com/tendermint/tmlibs/log" sm "github.com/tendermint/basecoin/state" @@ -53,7 +53,15 @@ func (app *Basecoin) GetState() *sm.State { // ABCI::Info func (app *Basecoin) Info() abci.ResponseInfo { - return abci.ResponseInfo{Data: Fmt("Basecoin v%v", version.Version)} + resp, err := app.eyesCli.InfoSync() + if err != nil { + cmn.PanicCrisis(err) + } + return abci.ResponseInfo{ + Data: cmn.Fmt("Basecoin v%v", version.Version), + LastBlockHeight: resp.LastBlockHeight, + LastBlockAppHash: resp.LastBlockAppHash, + } } func (app *Basecoin) RegisterPlugin(plugin types.Plugin) { @@ -172,7 +180,7 @@ func (app *Basecoin) Commit() (res abci.Result) { app.cacheState = app.state.CacheWrap() if res.IsErr() { - PanicSanity("Error getting hash: " + res.Error()) + cmn.PanicSanity("Error getting hash: " + res.Error()) } return res } diff --git a/app/genesis.go b/app/genesis.go index fa6a5ac3053..3901fa68985 100644 --- a/app/genesis.go +++ b/app/genesis.go @@ -37,6 +37,7 @@ func (app *Basecoin) LoadGenesis(path string) error { r := app.SetOption(kv.Key, kv.Value) app.logger.Info("Done setting Plugin key-value pair via SetOption", "result", r, "k", kv.Key, "v", kv.Value) } + return nil } @@ -71,6 +72,10 @@ func loadGenesis(filePath string) (*FullGenesisDoc, error) { return nil, errors.Wrap(err, "unmarshaling genesis file") } + if genDoc.AppOptions == nil { + genDoc.AppOptions = new(GenesisDoc) + } + pluginOpts, err := parseGenesisList(genDoc.AppOptions.PluginOptions) if err != nil { return nil, err diff --git a/app/genesis_test.go b/app/genesis_test.go index b2a590bcf3d..dc398afd971 100644 --- a/app/genesis_test.go +++ b/app/genesis_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/tendermint/basecoin/types" - "github.com/tendermint/go-crypto" + crypto "github.com/tendermint/go-crypto" eyescli "github.com/tendermint/merkleeyes/client" cmn "github.com/tendermint/tmlibs/common" ) @@ -16,6 +16,13 @@ import ( const genesisFilepath = "./testdata/genesis.json" const genesisAcctFilepath = "./testdata/genesis2.json" +func TestLoadGenesisDoNotFailIfAppOptionsAreMissing(t *testing.T) { + eyesCli := eyescli.NewLocalClient("", 0) + app := NewBasecoin(eyesCli) + err := app.LoadGenesis("./testdata/genesis3.json") + require.Nil(t, err, "%+v", err) +} + func TestLoadGenesis(t *testing.T) { assert, require := assert.New(t), require.New(t) diff --git a/app/testdata/genesis3.json b/app/testdata/genesis3.json new file mode 100644 index 00000000000..b58b1d74025 --- /dev/null +++ b/app/testdata/genesis3.json @@ -0,0 +1,3 @@ +{ + "chain_id": "foo_bar_chain" +} diff --git a/circle.yml b/circle.yml index 3e8710ca9e1..3034760d622 100644 --- a/circle.yml +++ b/circle.yml @@ -19,9 +19,5 @@ dependencies: test: override: - - "cd $REPO && glide install && go install ./cmd/basecoin" + - "cd $REPO && make all" - 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 index bb58ebb19d6..002c96cdf73 100644 --- a/cmd/basecli/LIGHT_NODE.md +++ b/cmd/basecli/LIGHT_NODE.md @@ -21,7 +21,7 @@ Just initialize your client with the proper validator set as in the [README](REA ``` $ export BCHOME=~/.lightnode -$ basecli init --node tcp://: --chainid +$ basecli init --node tcp://: --chain-id ``` ## Running diff --git a/cmd/basecli/README.md b/cmd/basecli/README.md index efa737512b9..2443c92ae50 100644 --- a/cmd/basecli/README.md +++ b/cmd/basecli/README.md @@ -4,13 +4,6 @@ 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 ``` @@ -24,30 +17,29 @@ 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 '"'` +% ME=$(basecli keys get demo | awk '{print $2}') +% YOU=$(basecli keys get buddy | awk '{print $2}') ``` -## Update genesis so you are rich, and start +## Set up a clean basecoin, initialized with your account ``` -$ vi $BCHOME/genesis.json --> cut/paste your pubkey from the results above - +$ export BCHOME=~/.demoserve +$ basecoin init $ME $ basecoin start ``` ## Connect your basecli the first time ``` -% basecli init --chainid test_chain_id --node tcp://localhost:46657 +% basecli init --chain-id 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 +% basecli query account $ME +% basecli query account $YOU ``` ## Send the money @@ -55,9 +47,7 @@ $ basecoin start ``` % 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 +% basecli query tx $HASH +% basecli query account $YOU ``` diff --git a/cmd/basecli/commands/adapters.go b/cmd/basecli/commands/adapters.go deleted file mode 100644 index 516cb2f0df3..00000000000 --- a/cmd/basecli/commands/adapters.go +++ /dev/null @@ -1,232 +0,0 @@ -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 index 7cf9e3c6a2e..acfc5932d19 100644 --- a/cmd/basecli/commands/apptx.go +++ b/cmd/basecli/commands/apptx.go @@ -51,9 +51,48 @@ func (s *AppTx) Signers() ([]crypto.PubKey, error) { func (s *AppTx) TxBytes() ([]byte, error) { // TODO: verify it is signed - // Code and comment from: basecoin/cmd/commands/tx.go + // Code and comment from: basecoin/cmd/basecoin/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 } + +// AddSigner sets address and pubkey info on the tx based on the key that +// will be used for signing +func (a *AppTx) AddSigner(pk crypto.PubKey) { + // get addr if available + var addr []byte + if !pk.Empty() { + addr = pk.Address() + } + + // set the send address, and pubkey if needed + in := &a.Tx.Input + in.Address = addr + if in.Sequence == 1 { + in.PubKey = pk + } +} + +// TODO: this should really be in the basecoin.types SendTx, +// but that code is too ugly now, needs refactor.. +func (a *AppTx) ValidateBasic() error { + if a.chainID == "" { + return errors.New("No chain-id specified") + } + in := a.Tx.Input + if len(in.Address) != 20 { + return errors.Errorf("Invalid input address length: %d", len(in.Address)) + } + if !in.Coins.IsValid() { + return errors.Errorf("Invalid input coins %v", in.Coins) + } + if in.Coins.IsZero() { + return errors.New("Input coins cannot be zero") + } + if in.Sequence <= 0 { + return errors.New("Sequence must be greater than 0") + } + return nil +} diff --git a/cmd/basecli/commands/cmds.go b/cmd/basecli/commands/cmds.go new file mode 100644 index 00000000000..8ac60bced52 --- /dev/null +++ b/cmd/basecli/commands/cmds.go @@ -0,0 +1,202 @@ +package commands + +import ( + "encoding/hex" + "strings" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + flag "github.com/spf13/pflag" + "github.com/spf13/viper" + + "github.com/tendermint/light-client/commands" + txcmd "github.com/tendermint/light-client/commands/txs" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + cmn "github.com/tendermint/tmlibs/common" + + btypes "github.com/tendermint/basecoin/types" +) + +//------------------------- +// SendTx + +// SendTxCmd is CLI command to send tokens between basecoin accounts +var SendTxCmd = &cobra.Command{ + Use: "send", + Short: "send tokens from one account to another", + RunE: doSendTx, +} + +//nolint +const ( + FlagTo = "to" + FlagAmount = "amount" + FlagFee = "fee" + FlagGas = "gas" + FlagSequence = "sequence" +) + +func init() { + flags := SendTxCmd.Flags() + flags.String(FlagTo, "", "Destination address for the bits") + flags.String(FlagAmount, "", "Coins to send in the format ,...") + flags.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format ") + flags.Int64(FlagGas, 0, "Amount of gas for this transaction") + flags.Int(FlagSequence, -1, "Sequence number for this transaction") +} + +// runDemo is an example of how to make a tx +func doSendTx(cmd *cobra.Command, args []string) error { + + // load data from json or flags + tx := new(btypes.SendTx) + found, err := txcmd.LoadJSON(tx) + if err != nil { + return err + } + if !found { + err = readSendTxFlags(tx) + } + if err != nil { + return err + } + + // Wrap and add signer + send := &SendTx{ + chainID: commands.GetChainID(), + Tx: tx, + } + send.AddSigner(txcmd.GetSigner()) + + // Sign if needed and post. This it the work-horse + bres, err := txcmd.SignAndPostTx(send) + if err != nil { + return err + } + + // Output result + return txcmd.OutputTx(bres) +} + +func readSendTxFlags(tx *btypes.SendTx) error { + // parse to address + to, err := parseChainAddress(viper.GetString(FlagTo)) + if err != nil { + return err + } + + //parse the fee and amounts into coin types + tx.Fee, err = btypes.ParseCoin(viper.GetString(FlagFee)) + if err != nil { + return err + } + amountCoins, err := btypes.ParseCoins(viper.GetString(FlagAmount)) + if err != nil { + return err + } + + // set the gas + tx.Gas = viper.GetInt64(FlagGas) + + // craft the inputs and outputs + tx.Inputs = []btypes.TxInput{{ + Coins: amountCoins, + Sequence: viper.GetInt(FlagSequence), + }} + tx.Outputs = []btypes.TxOutput{{ + Address: to, + Coins: amountCoins, + }} + + return nil +} + +func parseChainAddress(toFlag string) ([]byte, 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 nil, errors.Errorf("To address has too many slashes") + } + + // convert destination address to bytes + to, err := hex.DecodeString(cmn.StripHex(toHex)) + if err != nil { + return nil, errors.Errorf("To address is invalid hex: %v\n", err) + } + + if chainPrefix != "" { + to = []byte(chainPrefix + "/" + string(to)) + } + return to, nil +} + +//------------------------- +// AppTx + +// BroadcastAppTx wraps, signs, and executes an app tx basecoin transaction +func BroadcastAppTx(tx *btypes.AppTx) (*ctypes.ResultBroadcastTxCommit, error) { + + // Generate app transaction to be broadcast + appTx := WrapAppTx(tx) + appTx.AddSigner(txcmd.GetSigner()) + + // Sign if needed and post to the node. This it the work-horse + return txcmd.SignAndPostTx(appTx) +} + +// AddAppTxFlags adds flags required by apptx +func AddAppTxFlags(fs *flag.FlagSet) { + fs.String(FlagAmount, "", "Coins to send in the format ,...") + fs.String(FlagFee, "0mycoin", "Coins for the transaction fee of the format ") + fs.Int64(FlagGas, 0, "Amount of gas for this transaction") + fs.Int(FlagSequence, -1, "Sequence number for this transaction") +} + +// ReadAppTxFlags reads in the standard flags +// your command should parse info to set tx.Name and tx.Data +func ReadAppTxFlags() (gas int64, fee btypes.Coin, txInput btypes.TxInput, err error) { + + // Set the gas + gas = viper.GetInt64(FlagGas) + + // Parse the fee and amounts into coin types + fee, err = btypes.ParseCoin(viper.GetString(FlagFee)) + if err != nil { + return + } + + // craft the inputs + var amount btypes.Coins + amount, err = btypes.ParseCoins(viper.GetString(FlagAmount)) + if err != nil { + return + } + txInput = btypes.TxInput{ + Coins: amount, + Sequence: viper.GetInt(FlagSequence), + } + + return +} + +// WrapAppTx wraps the transaction with chain id +func WrapAppTx(tx *btypes.AppTx) *AppTx { + return &AppTx{ + chainID: commands.GetChainID(), + Tx: tx, + } +} + +/** TODO copied from basecoin cli - put in common somewhere? **/ + +// ParseHexFlag parses a flag string to byte array +func ParseHexFlag(flag string) ([]byte, error) { + return hex.DecodeString(cmn.StripHex(viper.GetString(flag))) +} diff --git a/cmd/basecli/commands/query.go b/cmd/basecli/commands/query.go new file mode 100644 index 00000000000..ac17cbdd92c --- /dev/null +++ b/cmd/basecli/commands/query.go @@ -0,0 +1,48 @@ +package commands + +import ( + "github.com/pkg/errors" + "github.com/spf13/cobra" + + wire "github.com/tendermint/go-wire" + lc "github.com/tendermint/light-client" + proofcmd "github.com/tendermint/light-client/commands/proofs" + "github.com/tendermint/light-client/proofs" + + btypes "github.com/tendermint/basecoin/types" +) + +var AccountQueryCmd = &cobra.Command{ + Use: "account [address]", + Short: "Get details of an account, with proof", + RunE: doAccountQuery, +} + +func doAccountQuery(cmd *cobra.Command, args []string) error { + addr, err := proofcmd.ParseHexKey(args, "address") + if err != nil { + return err + } + key := btypes.AccountKey(addr) + + acc := new(btypes.Account) + proof, err := proofcmd.GetAndParseAppProof(key, &acc) + if lc.IsNoDataErr(err) { + return errors.Errorf("Account bytes are empty for address %X ", addr) + } else if err != nil { + return err + } + + return proofcmd.OutputProof(acc, proof.BlockHeight()) +} + +// BaseTxPresenter this decodes all basecoin tx +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 +} diff --git a/cmd/basecli/commands/sendtx.go b/cmd/basecli/commands/sendtx.go index 0660ed22c3d..1a338ef29ee 100644 --- a/cmd/basecli/commands/sendtx.go +++ b/cmd/basecli/commands/sendtx.go @@ -50,7 +50,7 @@ func (s *SendTx) Signers() ([]crypto.PubKey, error) { func (s *SendTx) TxBytes() ([]byte, error) { // TODO: verify it is signed - // Code and comment from: basecoin/cmd/commands/tx.go + // Code and comment from: basecoin/cmd/basecoin/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 { @@ -58,3 +58,56 @@ func (s *SendTx) TxBytes() ([]byte, error) { }{s.Tx}) return txBytes, nil } + +// AddSigner sets address and pubkey info on the tx based on the key that +// will be used for signing +func (s *SendTx) AddSigner(pk crypto.PubKey) { + // get addr if available + var addr []byte + if !pk.Empty() { + addr = pk.Address() + } + + // set the send address, and pubkey if needed + in := s.Tx.Inputs + in[0].Address = addr + if in[0].Sequence == 1 { + in[0].PubKey = pk + } +} + +// TODO: this should really be in the basecoin.types SendTx, +// but that code is too ugly now, needs refactor.. +func (s *SendTx) ValidateBasic() error { + if s.chainID == "" { + return errors.New("No chain-id specified") + } + for _, in := range s.Tx.Inputs { + if len(in.Address) != 20 { + return errors.Errorf("Invalid input address length: %d", len(in.Address)) + } + if !in.Coins.IsValid() { + return errors.Errorf("Invalid input coins %v", in.Coins) + } + if in.Coins.IsZero() { + return errors.New("Input coins cannot be zero") + } + if in.Sequence <= 0 { + return errors.New("Sequence must be greater than 0") + } + } + for _, out := range s.Tx.Outputs { + // we now allow chain/addr, so it can be more than 20 bytes + if len(out.Address) < 20 { + return errors.Errorf("Invalid output address length: %d", len(out.Address)) + } + if !out.Coins.IsValid() { + return errors.Errorf("Invalid output coins %v", out.Coins) + } + if out.Coins.IsZero() { + return errors.New("Output coins cannot be zero") + } + } + + return nil +} diff --git a/cmd/basecli/counter/counter.go b/cmd/basecli/counter/counter.go deleted file mode 100644 index 3576e1629a1..00000000000 --- a/cmd/basecli/counter/counter.go +++ /dev/null @@ -1,85 +0,0 @@ -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 index 01dc6468329..50e47cb1baf 100644 --- a/cmd/basecli/main.go +++ b/cmd/basecli/main.go @@ -14,7 +14,7 @@ import ( "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" - bcount "github.com/tendermint/basecoin/cmd/basecli/counter" + coincmd "github.com/tendermint/basecoin/cmd/basecoin/commands" ) // BaseCli represents the base command when called without any subcommands @@ -32,22 +32,28 @@ 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{}) + // Prepare queries + pr := proofs.RootCmd + // These are default parsers, but optional in your app (you can remove key) + pr.AddCommand(proofs.TxCmd) + pr.AddCommand(proofs.KeyCmd) + pr.AddCommand(bcmd.AccountQueryCmd) - txs.Register("send", bcmd.SendTxMaker{}) - txs.Register("counter", bcount.CounterTxMaker{}) + // you will always want this for the base send command + proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) + tr := txs.RootCmd + tr.AddCommand(bcmd.SendTxCmd) - // set up the various commands to use + // Set up the various commands to use BaseCli.AddCommand( - keycmd.RootCmd, commands.InitCmd, + commands.ResetCmd, + keycmd.RootCmd, seeds.RootCmd, - proofs.RootCmd, - txs.RootCmd, + pr, + tr, proxy.RootCmd, + coincmd.VersionCmd, ) cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli")) diff --git a/cmd/basecoin/commands/ibc.go b/cmd/basecoin/commands/ibc.go new file mode 100644 index 00000000000..b76eae90236 --- /dev/null +++ b/cmd/basecoin/commands/ibc.go @@ -0,0 +1,8 @@ +package commands + +import "github.com/tendermint/basecoin/plugins/ibc" + +// returns a new IBC plugin to be registered with Basecoin +func NewIBCPlugin() *ibc.IBCPlugin { + return ibc.New() +} diff --git a/cmd/commands/init.go b/cmd/basecoin/commands/init.go similarity index 70% rename from cmd/commands/init.go rename to cmd/basecoin/commands/init.go index 2cfbbcd94ab..e9f20247ac5 100644 --- a/cmd/commands/init.go +++ b/cmd/basecoin/commands/init.go @@ -1,6 +1,7 @@ package commands import ( + "fmt" "io/ioutil" "os" "path" @@ -17,6 +18,18 @@ var ( } ) +//flags +var ( + chainIDFlag string +) + +func init() { + flags := []Flag2Register{ + {&chainIDFlag, "chain-id", "test_chain_id", "Chain ID"}, + } + RegisterFlags(InitCmd, flags) +} + // returns 1 iff it set a file, otherwise 0 (so we can add them) func setupFile(path, data string, perm os.FileMode) (int, error) { _, err := os.Stat(path) @@ -37,13 +50,17 @@ func initCmd(cmd *cobra.Command, args []string) error { return err } + if len(args) != 1 { + return fmt.Errorf("`init` takes one argument, a basecoin account address. Generate one using `basecli keys new mykey`") + } + userAddr := args[0] + // initalize basecoin genesisFile := cfg.GenesisFile() privValFile := cfg.PrivValidatorFile() - key1File := path.Join(cfg.RootDir, "key.json") - key2File := path.Join(cfg.RootDir, "key2.json") + keyFile := path.Join(cfg.RootDir, "key.json") - mod1, err := setupFile(genesisFile, GenesisJSON, 0644) + mod1, err := setupFile(genesisFile, GetGenesisJSON(chainIDFlag, userAddr), 0644) if err != nil { return err } @@ -51,17 +68,13 @@ func initCmd(cmd *cobra.Command, args []string) error { if err != nil { return err } - mod3, err := setupFile(key1File, Key1JSON, 0400) - if err != nil { - return err - } - mod4, err := setupFile(key2File, Key2JSON, 0400) + mod3, err := setupFile(keyFile, KeyJSON, 0400) if err != nil { return err } - if (mod1 + mod2 + mod3 + mod4) > 0 { - logger.Info("Initialized Basecoin", "genesis", genesisFile, "key", key1File) + if (mod1 + mod2 + mod3) > 0 { + logger.Info("Initialized Basecoin", "genesis", genesisFile, "priv_validator", privValFile) } else { logger.Info("Already initialized", "priv_validator", privValFile) } @@ -86,9 +99,13 @@ var PrivValJSON = `{ } }` -var GenesisJSON = `{ +// GetGenesisJSON returns a new tendermint genesis with Basecoin app_options +// that grant a large amount of "mycoin" to a single address +// TODO: A better UX for generating genesis files +func GetGenesisJSON(chainID, addr string) string { + return fmt.Sprintf(`{ "app_hash": "", - "chain_id": "test_chain_id", + "chain_id": "%s", "genesis_time": "0001-01-01T00:00:00.000Z", "validators": [ { @@ -102,10 +119,7 @@ var GenesisJSON = `{ ], "app_options": { "accounts": [{ - "pub_key": { - "type": "ed25519", - "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" - }, + "address": "%s", "coins": [ { "denom": "mycoin", @@ -114,9 +128,11 @@ var GenesisJSON = `{ ] }] } -}` +}`, chainID, addr) +} -var Key1JSON = `{ +// TODO: remove this once not needed for relay +var KeyJSON = `{ "address": "1B1BE55F969F54064628A63B9559E7C21C925165", "priv_key": { "type": "ed25519", @@ -127,15 +143,3 @@ var Key1JSON = `{ "data": "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" } }` - -var Key2JSON = `{ - "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", - "priv_key": { - "type": "ed25519", - "data": "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A8352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" - }, - "pub_key": { - "type": "ed25519", - "data": "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" - } -}` diff --git a/cmd/commands/key.go b/cmd/basecoin/commands/key.go similarity index 63% rename from cmd/commands/key.go rename to cmd/basecoin/commands/key.go index 4f0b2538188..08f54e0a421 100644 --- a/cmd/commands/key.go +++ b/cmd/basecoin/commands/key.go @@ -9,42 +9,13 @@ import ( "strings" //"github.com/pkg/errors" - "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/go-crypto" "github.com/tendermint/tmlibs/cli" ) -//commands -var ( - KeyCmd = &cobra.Command{ - Use: "key", - Short: "Manage keys", - } - - NewKeyCmd = &cobra.Command{ - Use: "new", - Short: "Create a new private key", - RunE: newKeyCmd, - } -) - -func newKeyCmd(cmd *cobra.Command, args []string) error { - key := genKey() - keyJSON, err := json.MarshalIndent(key, "", "\t") - if err != nil { - return err - } - fmt.Println(string(keyJSON)) - return nil -} - -func init() { - //register commands - KeyCmd.AddCommand(NewKeyCmd) -} - //--------------------------------------------- // simple implementation of a key @@ -74,20 +45,6 @@ func (k *Key) Sign(msg []byte) crypto.Signature { return k.PrivKey.Sign(msg) } -// Generates a new validator with private key. -func genKey() *Key { - privKey := crypto.GenPrivKeyEd25519() - pubKey := privKey.PubKey() - addrBytes := pubKey.Address() - var addr Address - copy(addr[:], addrBytes) - return &Key{ - Address: addr, - PubKey: pubKey, - PrivKey: privKey.Wrap(), - } -} - func LoadKey(keyFile string) (*Key, error) { filePath := keyFile diff --git a/cmd/commands/plugin_util.go b/cmd/basecoin/commands/plugin_util.go similarity index 68% rename from cmd/commands/plugin_util.go rename to cmd/basecoin/commands/plugin_util.go index 082ff2669fc..d4f47b323a2 100644 --- a/cmd/commands/plugin_util.go +++ b/cmd/basecoin/commands/plugin_util.go @@ -19,16 +19,6 @@ func RegisterStartPlugin(name string, newPlugin func() types.Plugin) { plugins = append(plugins, plugin{name: name, newPlugin: newPlugin}) } -// Register a subcommand of QueryCmd for plugin specific query functionality -func RegisterQuerySubcommand(cmd *cobra.Command) { - QueryCmd.AddCommand(cmd) -} - -// Register a subcommand of TxCmd to craft transactions for plugins -func RegisterTxSubcommand(cmd *cobra.Command) { - TxCmd.AddCommand(cmd) -} - //Returns a version command based on version input func QuickVersionCmd(version string) *cobra.Command { return &cobra.Command{ diff --git a/cmd/commands/relay.go b/cmd/basecoin/commands/relay.go similarity index 80% rename from cmd/commands/relay.go rename to cmd/basecoin/commands/relay.go index 5a2cacfc963..b9ae47afa4a 100644 --- a/cmd/commands/relay.go +++ b/cmd/basecoin/commands/relay.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "io/ioutil" "strconv" "time" @@ -24,8 +25,19 @@ import ( var RelayCmd = &cobra.Command{ Use: "relay", + Short: "Relay ibc packets between two chains", +} + +var RelayStartCmd = &cobra.Command{ + Use: "start", Short: "Start basecoin relayer to relay IBC packets between chains", - RunE: relayCmd, + RunE: relayStartCmd, +} + +var RelayInitCmd = &cobra.Command{ + Use: "init", + Short: "Register both chains with each other, to prepare the relayer to run", + RunE: relayInitCmd, } //flags @@ -37,10 +49,12 @@ var ( chain2IDFlag string fromFileFlag string + + genesisFile1Flag string + genesisFile2Flag 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"}, @@ -48,14 +62,63 @@ func init() { {&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) + RegisterPersistentFlags(RelayCmd, flags) + + initFlags := []Flag2Register{ + {&genesisFile1Flag, "genesis1", "", "Path to genesis file for chain1"}, + {&genesisFile2Flag, "genesis2", "", "Path to genesis file for chain2"}, + } + RegisterFlags(RelayInitCmd, initFlags) + + RelayCmd.AddCommand(RelayStartCmd) + RelayCmd.AddCommand(RelayInitCmd) +} + +func relayStartCmd(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 +} + +func relayInitCmd(cmd *cobra.Command, args []string) error { + err := registerChain(chain1IDFlag, chain1AddrFlag, chain2IDFlag, genesisFile2Flag, fromFileFlag) + if err != nil { + return err + } + err = registerChain(chain2IDFlag, chain2AddrFlag, chain1IDFlag, genesisFile1Flag, fromFileFlag) + return err +} + +func registerChain(chainID, node, registerChainID, registerGenesis, keyFile string) error { + genesisBytes, err := ioutil.ReadFile(registerGenesis) + if err != nil { + return errors.Errorf("Error reading genesis file %v: %v\n", registerGenesis, err) + } + + ibcTx := ibc.IBCRegisterChainTx{ + ibc.BlockchainGenesis{ + ChainID: registerChainID, + Genesis: string(genesisBytes), + }, + } + + privKey, err := LoadKey(keyFile) + if err != nil { + return err + } + relay := newRelayer(privKey, chainID, node) + return relay.appTx(ibcTx) } func loop(addr1, addr2, id1, id2 string) { nextSeq := 0 // load the priv key - privKey, err := LoadKey(fromFlag) + privKey, err := LoadKey(fromFileFlag) if err != nil { logger.Error(err.Error()) cmn.PanicCrisis(err.Error()) @@ -233,15 +296,3 @@ func broadcastTxWithClient(httpClient *client.HTTP, tx tmtypes.Tx) ([]byte, stri 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/basecoin/commands/reset.go similarity index 100% rename from cmd/commands/reset.go rename to cmd/basecoin/commands/reset.go diff --git a/cmd/commands/root.go b/cmd/basecoin/commands/root.go similarity index 100% rename from cmd/commands/root.go rename to cmd/basecoin/commands/root.go diff --git a/cmd/commands/start.go b/cmd/basecoin/commands/start.go similarity index 100% rename from cmd/commands/start.go rename to cmd/basecoin/commands/start.go diff --git a/cmd/commands/utils.go b/cmd/basecoin/commands/utils.go similarity index 100% rename from cmd/commands/utils.go rename to cmd/basecoin/commands/utils.go diff --git a/cmd/commands/utils_test.go b/cmd/basecoin/commands/utils_test.go similarity index 100% rename from cmd/commands/utils_test.go rename to cmd/basecoin/commands/utils_test.go diff --git a/cmd/commands/version.go b/cmd/basecoin/commands/version.go similarity index 100% rename from cmd/commands/version.go rename to cmd/basecoin/commands/version.go diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 5bc03e0fa66..23c46c76807 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -5,7 +5,7 @@ import ( "github.com/spf13/cobra" - "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/basecoin/cmd/basecoin/commands" "github.com/tendermint/tmlibs/cli" ) @@ -19,12 +19,6 @@ func main() { commands.InitCmd, commands.StartCmd, commands.RelayCmd, - commands.TxCmd, - commands.QueryCmd, - commands.KeyCmd, - commands.VerifyCmd, - commands.BlockCmd, - commands.AccountCmd, commands.UnsafeResetAllCmd, commands.VersionCmd, ) diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go deleted file mode 100644 index 8f1bdb4f56b..00000000000 --- a/cmd/commands/ibc.go +++ /dev/null @@ -1,290 +0,0 @@ -package commands - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "io/ioutil" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/tendermint/basecoin/plugins/ibc" - - "github.com/tendermint/go-wire" - "github.com/tendermint/merkleeyes/iavl" - tmtypes "github.com/tendermint/tendermint/types" -) - -// returns a new IBC plugin to be registered with Basecoin -func NewIBCPlugin() *ibc.IBCPlugin { - return ibc.New() -} - -//commands -var ( - IBCTxCmd = &cobra.Command{ - Use: "ibc", - Short: "An IBC transaction, for InterBlockchain Communication", - } - - IBCRegisterTxCmd = &cobra.Command{ - Use: "register", - Short: "Register a blockchain via IBC", - RunE: ibcRegisterTxCmd, - } - - IBCUpdateTxCmd = &cobra.Command{ - Use: "update", - Short: "Update the latest state of a blockchain via IBC", - RunE: ibcUpdateTxCmd, - } - - IBCPacketTxCmd = &cobra.Command{ - Use: "packet", - Short: "Send a new packet via IBC", - } - - IBCPacketCreateTxCmd = &cobra.Command{ - Use: "create", - Short: "Create an egress IBC packet", - RunE: ibcPacketCreateTxCmd, - } - - IBCPacketPostTxCmd = &cobra.Command{ - Use: "post", - Short: "Deliver an IBC packet to another chain", - RunE: ibcPacketPostTxCmd, - } -) - -//flags -var ( - ibcChainIDFlag string - ibcGenesisFlag string - ibcHeaderFlag string - ibcCommitFlag string - ibcFromFlag string - ibcToFlag string - ibcTypeFlag string - ibcPayloadFlag string - ibcPacketFlag string - ibcProofFlag string - ibcSequenceFlag int - ibcHeightFlag int -) - -func init() { - - // register flags - registerFlags := []Flag2Register{ - {&ibcChainIDFlag, "ibc_chain_id", "", "ChainID for the new blockchain"}, - {&ibcGenesisFlag, "genesis", "", "Genesis file for the new blockchain"}, - } - - updateFlags := []Flag2Register{ - {&ibcHeaderFlag, "header", "", "Block header for an ibc update"}, - {&ibcCommitFlag, "commit", "", "Block commit for an ibc update"}, - } - - fromFlagReg := Flag2Register{&ibcFromFlag, "ibc_from", "", "Source ChainID"} - - packetCreateFlags := []Flag2Register{ - fromFlagReg, - {&ibcToFlag, "to", "", "Destination ChainID"}, - {&ibcTypeFlag, "type", "", "IBC packet type (eg. coin)"}, - {&ibcPayloadFlag, "payload", "", "IBC packet payload"}, - {&ibcSequenceFlag, "ibc_sequence", -1, "sequence number for IBC packet"}, - } - - packetPostFlags := []Flag2Register{ - fromFlagReg, - {&ibcHeightFlag, "height", 0, "Height the packet became egress in source chain"}, - {&ibcPacketFlag, "packet", "", "hex-encoded IBC packet"}, - {&ibcProofFlag, "proof", "", "hex-encoded proof of IBC packet from source chain"}, - } - - RegisterFlags(IBCRegisterTxCmd, registerFlags) - RegisterFlags(IBCUpdateTxCmd, updateFlags) - RegisterFlags(IBCPacketCreateTxCmd, packetCreateFlags) - RegisterFlags(IBCPacketPostTxCmd, packetPostFlags) - - //register commands - IBCTxCmd.AddCommand(IBCRegisterTxCmd, IBCUpdateTxCmd, IBCPacketTxCmd) - IBCPacketTxCmd.AddCommand(IBCPacketCreateTxCmd, IBCPacketPostTxCmd) - RegisterTxSubcommand(IBCTxCmd) -} - -//--------------------------------------------------------------------- -// ibc command implementations - -func ibcRegisterTxCmd(cmd *cobra.Command, args []string) error { - chainID := ibcChainIDFlag - genesisFile := ibcGenesisFlag - - genesisBytes, err := ioutil.ReadFile(genesisFile) - if err != nil { - return errors.Errorf("Error reading genesis file %v: %v\n", genesisFile, err) - } - - ibcTx := ibc.IBCRegisterChainTx{ - ibc.BlockchainGenesis{ - ChainID: chainID, - Genesis: string(genesisBytes), - }, - } - - out, err := json.Marshal(ibcTx) - if err != nil { - return err - } - - fmt.Printf("IBCTx: %s\n", string(out)) - - data := []byte(wire.BinaryBytes(struct { - ibc.IBCTx `json:"unwrap"` - }{ibcTx})) - name := "IBC" - - return AppTx(name, data) -} - -func ibcUpdateTxCmd(cmd *cobra.Command, args []string) error { - headerBytes, err := hex.DecodeString(StripHex(ibcHeaderFlag)) - if err != nil { - return errors.Errorf("Header (%v) is invalid hex: %v\n", ibcHeaderFlag, err) - } - - commitBytes, err := hex.DecodeString(StripHex(ibcCommitFlag)) - if err != nil { - return errors.Errorf("Commit (%v) is invalid hex: %v\n", ibcCommitFlag, err) - } - - header := new(tmtypes.Header) - commit := new(tmtypes.Commit) - - err = wire.ReadBinaryBytes(headerBytes, &header) - if err != nil { - return errors.Errorf("Error unmarshalling header: %v\n", err) - } - - err = wire.ReadBinaryBytes(commitBytes, &commit) - if err != nil { - return errors.Errorf("Error unmarshalling commit: %v\n", err) - } - - ibcTx := ibc.IBCUpdateChainTx{ - Header: *header, - Commit: *commit, - } - - out, err := json.Marshal(ibcTx) - if err != nil { - return err - } - fmt.Printf("IBCTx: %s\n", string(out)) - - data := []byte(wire.BinaryBytes(struct { - ibc.IBCTx `json:"unwrap"` - }{ibcTx})) - name := "IBC" - - return AppTx(name, data) -} - -func ibcPacketCreateTxCmd(cmd *cobra.Command, args []string) error { - fromChain, toChain := ibcFromFlag, ibcToFlag - packetType := ibcTypeFlag - - payloadBytes, err := hex.DecodeString(StripHex(ibcPayloadFlag)) - if err != nil { - return errors.Errorf("Payload (%v) is invalid hex: %v\n", ibcPayloadFlag, err) - } - - sequence, err := ibcSequenceCmd() - if err != nil { - 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: payload, - }, - } - - out, err := json.Marshal(ibcTx) - if err != nil { - return err - } - fmt.Printf("IBCTx: %s\n", string(out)) - - data := []byte(wire.BinaryBytes(struct { - ibc.IBCTx `json:"unwrap"` - }{ibcTx})) - - return AppTx("IBC", data) -} - -func ibcPacketPostTxCmd(cmd *cobra.Command, args []string) error { - fromChain, fromHeight := ibcFromFlag, ibcHeightFlag - - packetBytes, err := hex.DecodeString(StripHex(ibcPacketFlag)) - if err != nil { - return errors.Errorf("Packet (%v) is invalid hex: %v\n", ibcPacketFlag, err) - } - - proofBytes, err := hex.DecodeString(StripHex(ibcProofFlag)) - if err != nil { - return errors.Errorf("Proof (%v) is invalid hex: %v\n", ibcProofFlag, err) - } - - var packet ibc.Packet - proof := new(iavl.IAVLProof) - - err = wire.ReadBinaryBytes(packetBytes, &packet) - if err != nil { - return errors.Errorf("Error unmarshalling packet: %v\n", err) - } - - err = wire.ReadBinaryBytes(proofBytes, &proof) - if err != nil { - return errors.Errorf("Error unmarshalling proof: %v\n", err) - } - - ibcTx := ibc.IBCPacketPostTx{ - FromChainID: fromChain, - FromChainHeight: uint64(fromHeight), - Packet: packet, - Proof: proof, - } - - out, err := json.Marshal(ibcTx) - if err != nil { - return err - } - fmt.Printf("IBCTx: %s\n", string(out)) - - data := []byte(wire.BinaryBytes(struct { - ibc.IBCTx `json:"unwrap"` - }{ibcTx})) - - return AppTx("IBC", data) -} - -func ibcSequenceCmd() (uint64, error) { - if ibcSequenceFlag >= 0 { - return uint64(ibcSequenceFlag), nil - } - - // TODO: get sequence - return 0, nil -} diff --git a/cmd/commands/query.go b/cmd/commands/query.go deleted file mode 100644 index e31d6254919..00000000000 --- a/cmd/commands/query.go +++ /dev/null @@ -1,234 +0,0 @@ -package commands - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "strconv" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/tendermint/go-wire" - "github.com/tendermint/go-wire/data" - "github.com/tendermint/merkleeyes/iavl" - "github.com/tendermint/tendermint/rpc/client" - tmtypes "github.com/tendermint/tendermint/types" -) - -//commands -var ( - QueryCmd = &cobra.Command{ - Use: "query [key]", - Short: "Query the merkle tree", - RunE: queryCmd, - } - - AccountCmd = &cobra.Command{ - Use: "account [address]", - Short: "Get details of an account", - RunE: accountCmd, - } - - BlockCmd = &cobra.Command{ - Use: "block [height]", - Short: "Get the header and commit of a block", - RunE: blockCmd, - } - - VerifyCmd = &cobra.Command{ - Use: "verify", - Short: "Verify the IAVL proof", - RunE: verifyCmd, - } -) - -//flags -var ( - nodeFlag string - proofFlag string - keyFlag string - valueFlag string - rootFlag string -) - -func init() { - - commonFlags := []Flag2Register{ - {&nodeFlag, "node", "tcp://localhost:46657", "Tendermint RPC address"}, - } - - verifyFlags := []Flag2Register{ - {&proofFlag, "proof", "", "hex-encoded IAVL proof"}, - {&keyFlag, "key", "", "key to the IAVL tree"}, - {&valueFlag, "value", "", "value in the IAVL tree"}, - {&rootFlag, "root", "", "root hash of the IAVL tree"}, - } - - RegisterFlags(QueryCmd, commonFlags) - RegisterFlags(AccountCmd, commonFlags) - RegisterFlags(BlockCmd, commonFlags) - RegisterFlags(VerifyCmd, verifyFlags) -} - -func queryCmd(cmd *cobra.Command, args []string) error { - - if len(args) != 1 { - return fmt.Errorf("query command requires an argument ([key])") //never stack trace - } - - keyString := args[0] - key := []byte(keyString) - if isHex(keyString) { - // convert key to bytes - var err error - key, err = hex.DecodeString(StripHex(keyString)) - if err != nil { - return errors.Errorf("Query key (%v) is invalid hex: %v\n", keyString, err) - } - } - - resp, err := Query(nodeFlag, key) - if err != nil { - return errors.Errorf("Query returns error: %v\n", err) - } - - if !resp.Code.IsOK() { - return errors.Errorf("Query for key (%v) returned non-zero code (%v): %v", keyString, resp.Code, resp.Log) - } - - val := resp.Value - proof := resp.Proof - height := resp.Height - - out, err := json.Marshal(struct { - Value data.Bytes `json:"value"` - Proof data.Bytes `json:"proof"` - Height uint64 `json:"height"` - }{val, proof, height}) - if err != nil { - return err - } - - fmt.Println(string(out)) - return nil -} - -func accountCmd(cmd *cobra.Command, args []string) error { - - if len(args) != 1 { - return fmt.Errorf("account command requires an argument ([address])") //never stack trace - } - - addrHex := StripHex(args[0]) - - // convert destination address to bytes - addr, err := hex.DecodeString(addrHex) - if err != nil { - return errors.Errorf("Account address (%v) is invalid hex: %v\n", addrHex, err) - } - - httpClient := client.NewHTTP(nodeFlag, "/websocket") - acc, err := getAccWithClient(httpClient, addr) - if err != nil { - return err - } - out, err := json.Marshal(acc) - if err != nil { - return err - } - fmt.Println(string(out)) - return nil -} - -func blockCmd(cmd *cobra.Command, args []string) error { - - if len(args) != 1 { - return fmt.Errorf("block command requires an argument ([height])") //never stack trace - } - - heightString := args[0] - height, err := strconv.Atoi(heightString) - if err != nil { - return errors.Errorf("Height must be an int, got %v: %v\n", heightString, err) - } - - header, commit, err := getHeaderAndCommit(nodeFlag, height) - if err != nil { - return err - } - - out, err := json.Marshal(struct { - Hex BlockHex `json:"hex"` - JSON BlockJSON `json:"json"` - }{ - BlockHex{ - Header: wire.BinaryBytes(header), - Commit: wire.BinaryBytes(commit), - }, - BlockJSON{ - Header: header, - Commit: commit, - }, - }) - if err != nil { - return err - } - - fmt.Println(string(out)) - return nil -} - -type BlockHex struct { - Header data.Bytes `json:"header"` - Commit data.Bytes `json:"commit"` -} - -type BlockJSON struct { - Header *tmtypes.Header `json:"header"` - Commit *tmtypes.Commit `json:"commit"` -} - -func verifyCmd(cmd *cobra.Command, args []string) error { - - keyString, valueString := keyFlag, valueFlag - - var err error - key := []byte(keyString) - if isHex(keyString) { - key, err = hex.DecodeString(StripHex(keyString)) - if err != nil { - return errors.Errorf("Key (%v) is invalid hex: %v\n", keyString, err) - } - } - - value := []byte(valueString) - if isHex(valueString) { - value, err = hex.DecodeString(StripHex(valueString)) - if err != nil { - return errors.Errorf("Value (%v) is invalid hex: %v\n", valueString, err) - } - } - - root, err := hex.DecodeString(StripHex(rootFlag)) - if err != nil { - return errors.Errorf("Root (%v) is invalid hex: %v\n", rootFlag, err) - } - - proofBytes, err := hex.DecodeString(StripHex(proofFlag)) - if err != nil { - return errors.Errorf("Proof (%v) is invalid hex: %v\n", proofFlag, err) - } - - proof, err := iavl.ReadProof(proofBytes) - if err != nil { - return errors.Errorf("Error unmarshalling proof: %v\n", err) - } - - if proof.Verify(key, value, root) { - fmt.Println("OK") - } else { - return errors.New("Proof does not verify") - } - return nil -} diff --git a/cmd/commands/tx.go b/cmd/commands/tx.go deleted file mode 100644 index 70bc8e626b2..00000000000 --- a/cmd/commands/tx.go +++ /dev/null @@ -1,260 +0,0 @@ -package commands - -import ( - "encoding/hex" - "fmt" - "strings" - - "github.com/pkg/errors" - "github.com/spf13/cobra" - - "github.com/tendermint/basecoin/types" - - wire "github.com/tendermint/go-wire" - "github.com/tendermint/tendermint/rpc/client" -) - -//commands -var ( - TxCmd = &cobra.Command{ - Use: "tx", - Short: "Create, sign, and broadcast a transaction", - } - - SendTxCmd = &cobra.Command{ - Use: "send", - Short: "A SendTx transaction, for sending tokens around", - RunE: sendTxCmd, - } - - AppTxCmd = &cobra.Command{ - Use: "app", - Short: "An AppTx transaction, for sending raw data to plugins", - RunE: appTxCmd, - } -) - -var ( - //persistent flags - txNodeFlag string - amountFlag string - fromFlag string - seqFlag int - gasFlag int - feeFlag string - chainIDFlag string - - //non-persistent flags - toFlag string - dataFlag string - nameFlag string -) - -func init() { - - // register flags - cmdTxFlags := []Flag2Register{ - {&txNodeFlag, "node", "tcp://localhost:46657", "Tendermint RPC address"}, - {&chainIDFlag, "chain_id", "test_chain_id", "ID of the chain for replay protection"}, - {&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", "0coin", "Coins for the transaction fee of the format "}, - {&seqFlag, "sequence", -1, "Sequence number for the account (-1 to autocalculate)"}, - } - - sendTxFlags := []Flag2Register{ - {&toFlag, "to", "", "Destination address for the transaction"}, - } - - appTxFlags := []Flag2Register{ - {&nameFlag, "name", "", "Plugin to send the transaction to"}, - {&dataFlag, "data", "", "Data to send with the transaction"}, - } - - RegisterPersistentFlags(TxCmd, cmdTxFlags) - RegisterFlags(SendTxCmd, sendTxFlags) - RegisterFlags(AppTxCmd, appTxFlags) - - //register commands - TxCmd.AddCommand(SendTxCmd, AppTxCmd) -} - -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(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 { - return err - } - - // get the sequence number for the tx - sequence, err := getSeq(privKey.Address[:]) - if err != nil { - return err - } - - //parse the fee and amounts into coin types - feeCoin, err := types.ParseCoin(feeFlag) - if err != nil { - return err - } - amountCoins, err := types.ParseCoins(amountFlag) - if err != nil { - return err - } - - // craft the tx - input := types.NewTxInput(privKey.PubKey, amountCoins, sequence) - output := newOutput(to, amountCoins) - tx := &types.SendTx{ - Gas: int64(gasFlag), - Fee: feeCoin, - Inputs: []types.TxInput{input}, - Outputs: []types.TxOutput{output}, - } - - // sign that puppy - signBytes := tx.SignBytes(chainIDFlag) - tx.Inputs[0].Signature = privKey.Sign(signBytes) - - out := wire.BinaryBytes(tx) - fmt.Println("Signed SendTx:") - fmt.Printf("%X\n", out) - - // broadcast the transaction to tendermint - data, log, err := broadcastTx(tx) - if err != nil { - return err - } - fmt.Printf("Response: %X ; %s\n", data, log) - return nil -} - -func appTxCmd(cmd *cobra.Command, args []string) error { - // convert data to bytes - data := []byte(dataFlag) - if isHex(dataFlag) { - data, _ = hex.DecodeString(dataFlag) - } - name := nameFlag - return AppTx(name, data) -} - -func AppTx(name string, data []byte) error { - - privKey, err := LoadKey(fromFlag) - if err != nil { - return err - } - - sequence, err := getSeq(privKey.Address[:]) - if err != nil { - return err - } - - //parse the fee and amounts into coin types - feeCoin, err := types.ParseCoin(feeFlag) - if err != nil { - return err - } - - amountCoins, err := types.ParseCoins(amountFlag) - if err != nil { - return err - } - - input := types.NewTxInput(privKey.PubKey, amountCoins, sequence) - tx := &types.AppTx{ - Gas: int64(gasFlag), - Fee: feeCoin, - Name: name, - Input: input, - Data: data, - } - - tx.Input.Signature = privKey.Sign(tx.SignBytes(chainIDFlag)) - - out := wire.BinaryBytes(tx) - fmt.Println("Signed AppTx:") - fmt.Printf("%X\n", out) - - data, log, err := broadcastTx(tx) - if err != nil { - return err - } - fmt.Printf("Response: %X ; %s\n", data, log) - return nil -} - -// broadcast the transaction to tendermint -func broadcastTx(tx types.Tx) ([]byte, string, error) { - 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})) - res, err := httpClient.BroadcastTxCommit(txBytes) - if err != nil { - return nil, "", errors.Errorf("Error on broadcast tx: %v", err) - } - - // if it fails check, we don't even get a delivertx back! - 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 -} - -// 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 - } - - httpClient := client.NewHTTP(txNodeFlag, "/websocket") - acc, err := getAccWithClient(httpClient, address) - if err != nil { - return 0, err - } - return acc.Sequence + 1, nil -} - -func newOutput(to []byte, amount types.Coins) types.TxOutput { - return types.TxOutput{ - Address: to, - Coins: amount, - } -} diff --git a/cmd/counter/cmd.go b/cmd/counter/cmd.go deleted file mode 100644 index e83430498a1..00000000000 --- a/cmd/counter/cmd.go +++ /dev/null @@ -1,59 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - - "github.com/spf13/cobra" - wire "github.com/tendermint/go-wire" - - "github.com/tendermint/basecoin/cmd/commands" - "github.com/tendermint/basecoin/plugins/counter" - "github.com/tendermint/basecoin/types" -) - -//commands -var CounterTxCmd = &cobra.Command{ - Use: "counter", - Short: "Create, sign, and broadcast a transaction to the counter plugin", - RunE: counterTxCmd, -} - -//flags -var ( - validFlag bool - countFeeFlag string -) - -func init() { - - CounterTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set valid field in CounterTx") - CounterTxCmd.Flags().StringVar(&countFeeFlag, "countfee", "", "Coins for the counter fee of the format ") - - commands.RegisterTxSubcommand(CounterTxCmd) - commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) -} - -func counterTxCmd(cmd *cobra.Command, args []string) error { - - countFee, err := types.ParseCoins(countFeeFlag) - if err != nil { - return err - } - - counterTx := counter.CounterTx{ - Valid: validFlag, - Fee: countFee, - } - - out, err := json.Marshal(counterTx) - if err != nil { - return err - } - fmt.Println("CounterTx:", string(out)) - - data := wire.BinaryBytes(counterTx) - name := "counter" - - return commands.AppTx(name, data) -} diff --git a/demo/clean.sh b/demo/clean.sh deleted file mode 100644 index bffdefdb84f..00000000000 --- a/demo/clean.sh +++ /dev/null @@ -1,10 +0,0 @@ -#! /bin/bash - -killall -9 basecoin tendermint -TMHOME=./data/chain1 tendermint unsafe_reset_all -TMHOME=./data/chain2 tendermint unsafe_reset_all - -rm ./*.log - -rm ./data/chain1/*.bak -rm ./data/chain2/*.bak diff --git a/demo/data/chain1/config.toml b/demo/data/chain1/config.toml deleted file mode 100644 index e2bcb49fff8..00000000000 --- a/demo/data/chain1/config.toml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -proxy_app = "tcp://127.0.0.1:46658" -moniker = "anonymous" -node_laddr = "tcp://0.0.0.0:46656" -seeds = "" -fast_sync = true -db_backend = "leveldb" -log_level = "info" -rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain1/genesis.json b/demo/data/chain1/genesis.json deleted file mode 100644 index 284572eb374..00000000000 --- a/demo/data/chain1/genesis.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "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 - } - ] - } - ] - } -} diff --git a/demo/data/chain1/key.json b/demo/data/chain1/key.json deleted file mode 100644 index 751dc858f0d..00000000000 --- a/demo/data/chain1/key.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", - "priv_key": { - "type": "ed25519", - "data": "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - }, - "pub_key": { - "type": "ed25519", - "data": "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - } -} - diff --git a/demo/data/chain1/priv_validator.json b/demo/data/chain1/priv_validator.json deleted file mode 100644 index 55db06f4bca..00000000000 --- a/demo/data/chain1/priv_validator.json +++ /dev/null @@ -1 +0,0 @@ -{"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 deleted file mode 100644 index e2bcb49fff8..00000000000 --- a/demo/data/chain2/config.toml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a TOML config file. -# For more information, see https://github.com/toml-lang/toml - -proxy_app = "tcp://127.0.0.1:46658" -moniker = "anonymous" -node_laddr = "tcp://0.0.0.0:46656" -seeds = "" -fast_sync = true -db_backend = "leveldb" -log_level = "info" -rpc_laddr = "tcp://0.0.0.0:46657" diff --git a/demo/data/chain2/genesis.json b/demo/data/chain2/genesis.json deleted file mode 100644 index 3ff94b99332..00000000000 --- a/demo/data/chain2/genesis.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "app_hash": "", - "chain_id": "test_chain_2", - "genesis_time": "0001-01-01T00:00:00.000Z", - "validators": [ - { - "amount": 10, - "name": "", - "pub_key": { - "type": "ed25519", - "data": "9A76DDE4CA4EE660C073D288DBE4F8A128F23857881A95F18167682D47E7058F" - } - } - ], - "app_options": { - "accounts": [ - { - "pub_key": { - "type": "ed25519", - "data": "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" - }, - "coins": [ - { - "denom": "mycoin", - "amount": 9007199254740992 - } - ] - } - ] - } -} diff --git a/demo/data/chain2/key.json b/demo/data/chain2/key.json deleted file mode 100644 index 6aa8b7965f9..00000000000 --- a/demo/data/chain2/key.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47", - "priv_key": { - "type": "ed25519", - "data": "22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" - }, - "pub_key": { - "type": "ed25519", - "data": "0628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" - } -} diff --git a/demo/data/chain2/priv_validator.json b/demo/data/chain2/priv_validator.json deleted file mode 100644 index 12eb625259f..00000000000 --- a/demo/data/chain2/priv_validator.json +++ /dev/null @@ -1 +0,0 @@ -{"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 deleted file mode 100644 index abb1576be54..00000000000 --- a/demo/start.sh +++ /dev/null @@ -1,198 +0,0 @@ -#! /bin/bash -set -e - -cd $GOPATH/src/github.com/tendermint/basecoin/demo - -LOG_DIR="." -TM_VERSION="master" -#TM_VERSION="v0.10.0" - -if [[ "$CIRCLECI" == "true" ]]; then - # set log dir - LOG_DIR="${CIRCLE_ARTIFACTS}" - - # install tendermint - set +e - go get github.com/tendermint/tendermint - pushd $GOPATH/src/github.com/tendermint/tendermint - git checkout $TM_VERSION - glide install - go install ./cmd/tendermint - popd - set -e -fi - -set -u - -function ifExit() { - if [[ "$?" != 0 ]]; then - echo "FAIL" - exit 1 - fi -} - -function removeQuotes() { - temp="${1%\"}" - temp="${temp#\"}" - echo "$temp" -} - -function waitForNode() { - addr=$1 - set +e - curl -s $addr/status > /dev/null - ERR=$? - i=0 - while [ "$ERR" != 0 ]; do - if [[ "$i" == 10 ]]; then - echo "waited too long for chain to start" - exit 1 - fi - echo "...... still waiting on $addr" - sleep 1 - curl -s $addr/status > /dev/null - ERR=$? - i=$((i+1)) - done - set -e - echo "... node $addr is up" -} - -function waitForBlock() { - addr=$1 - 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.latest_block_height` - done -} - -# make basecoin root vars -export BCHOME="." -BCHOME1="./data/chain1" -BCHOME2="./data/chain2" - -# grab the chain ids -CHAIN_ID1=$(cat $BCHOME1/genesis.json | jq .chain_id) -CHAIN_ID1=$(removeQuotes $CHAIN_ID1) -CHAIN_ID2=$(cat $BCHOME2/genesis.json | jq .chain_id) -CHAIN_ID2=$(removeQuotes $CHAIN_ID2) -echo "CHAIN_ID1: $CHAIN_ID1" -echo "CHAIN_ID2: $CHAIN_ID2" - -# make reusable chain flags -CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json" -CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657" - - -echo "" -echo "... starting chains" -echo "" -# start the first node -TMHOME=$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 -TMHOME=$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" -echo "" - -waitForNode localhost:46657 -waitForNode localhost:36657 - -# TODO: remove the sleep -# Without it we sometimes get "Account bytes are empty for address: 053BA0F19616AFF975C8756A2CBFF04F408B4D47" -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 "" -# 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 -# 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) -PACKET=$(removeQuotes $PACKET) -PROOF=$(removeQuotes $PROOF) -echo "" -echo "QUERY_RESULT: $QUERY_RESULT" -echo "HEIGHT: $HEIGHT" -echo "PACKET: $PACKET" -echo "PROOF: $PROOF" - - -# the query returns the height of the next block, which contains the app hash -# but which may not be committed yet, so we have to wait for it to query the commit -echo "" -echo "... waiting for a block to be committed" -echo "" - -waitForBlock localhost:46657 -waitForBlock localhost:36657 - -echo "" -echo "... querying for block data" -echo "" -# get the header and commit for the 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) -COMMIT=$(removeQuotes $COMMIT) -echo "" -echo "HEADER_AND_COMMIT: $HEADER_AND_COMMIT" -echo "HEADER: $HEADER" -echo "COMMIT: $COMMIT" - -echo "" -echo "... updating state of chain1 on chain2" -echo "" -# update the state of chain1 on chain2 -basecoin tx ibc --amount 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,0 -ifExit - -echo "" -echo "DONE!" diff --git a/docs/go_basics.md b/docs/go_basics.md index 4744b1bff38..8dd00ed23c9 100644 --- a/docs/go_basics.md +++ b/docs/go_basics.md @@ -100,12 +100,12 @@ make test Great! Now when I run `tendermint` I have the newest of the new, the develop branch! But please note that this branch is not considered production ready and may have issues. This should only be done if you want to develop code for the future and run locally. -But wait, I want to mix and match. There is a bugfix in `go-p2p:persistent_peer` that I want to use with tendermint. How to compile this. I will show with a simple example, please update the repo and commit numbers for your usecase. Also, make sure these branches are compatible, so if `persistent_peer` is close to `master` it should work. But if it is 15 commits ahead, you will probably need the `develop` branch of tendermint to compile with it. But I assume you know your way around git and can figure that out. +But wait, I want to mix and match. There is a bugfix in `go-crypto:unstable` 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 `unstable` 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. In the dependent repo: ``` -cd $GOPATH/src/github.com/tendermint/go-p2p -git checkout persistent_peer +cd $GOPATH/src/github.com/tendermint/go-crypto +git checkout unstable git pull # double-check this makes sense or if it is too far off git log --oneline --decorate --graph @@ -115,10 +115,10 @@ git log | head -1 In the main repo (tendermint, basecoin, ...) where the binary will be built: ``` -cd $GOPATH/src/github.com/tendermint/tendermin +cd $GOPATH/src/github.com/tendermint/tendermint git checkout master git pull -# -> edit glide.lock, set the version of go-p2p (for example) +# -> edit glide.lock, set the version of go-crypto (for example) # to the commit number you got above (the 40 char version) make get_vendor_deps make install diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index c2b974df93f..716fddb9708 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -1,33 +1,65 @@ # Basecoin Basics -Here we explain how to get started with a simple Basecoin blockchain, +Here we explain how to get started with a simple Basecoin blockchain, how to send transactions between accounts using the `basecoin` tool, and what is happening under the hood. ## Install -Installing basecoin is simple: +Installing Basecoin is simple: ``` -go get -u github.com/tendermint/basecoin/cmd/basecoin +go get -u github.com/tendermint/basecoin/cmd/... ``` If you have trouble, see the [installation guide](install.md). -## Initialization +Note the above command installs two binaries: `basecoin` and `basecli`. +The former is the running node. The latter is a command-line light-client. + +## Generate some keys + +Let's generate two keys, one to receive an initial allocation of coins, +and one to send some coins to later: + +``` +# WARNING: this will wipe out any existing info in the ~/.basecli dir +# including private keys, don't run if you have lots of local state already +basecli reset_all +basecli keys new cool +basecli keys new friend +``` + +You'll need to enter passwords. You can view your key names and addresses with `basecli keys list`, +or see a particular key's address with `basecli keys get `. + +## Initialize Basecoin + To initialize a new Basecoin blockchain, run: ``` -basecoin init +# WARNING: this will wipe out any existing info in the ~/.basecoin dir +# don't run if you have lots of local state already +rm -rf ~/.basecoin +basecoin init
``` -This will create the necessary files for a Basecoin blockchain with one validator and one account in `~/.basecoin`. -For more options on setup, see the [guide to using the Basecoin tool](/docs/guide/basecoin-tool.md). +If you prefer not to copy-paste, you can provide the address programatically: + +``` +basecoin init $(basecli keys get cool | awk '{print $2}') +``` + +This will create the necessary files for a Basecoin blockchain with one +validator and one account (corresponding to your key) in `~/.basecoin`. For more options on setup, see the +[guide to using the Basecoin tool](/docs/guide/basecoin-tool.md). + +If you like, you can manually add some more accounts to the blockchain by generating keys and editing the `~/.basecoin/genesis.json`. ## Start -Now we can start basecoin: +Now we can start Basecoin: ``` basecoin start @@ -35,64 +67,102 @@ basecoin start You should see blocks start streaming in! -## Send transactions +## Initialize Light-Client -Now we are ready to send some transactions. First, open another window. -If you take a look at the `~/.basecoin/genesis.json` file, you will see one account listed under the `app_options`. -This account corresponds to the private key in `~/.basecoin/key.json`. -We also included the private key for another account, in `~/.basecoin/key2.json`. +Now that Basecoin is running we can initialize `basecli`, the light-client utility. +Basecli is used for sending transactions and querying the state. +Leave Basecoin running and open a new terminal window. Here run: -Leave basecoin running and open a new terminal window. -Let's check the balance of these two accounts: +``` +basecli init --chain-id=test_chain_id --node=tcp://localhost:46657 +``` + +Note it will ask you to verify the validator hash. For a blockchain on your local computer, don't worry about it. +If you're connecting to a blockchain over the internet, you should verify that the validator hash is correct. +This is so that all queries done with `basecli` can be cryptographically proven to be correct according to a known validator set. + +## Send transactions + +Now we are ready to send some transactions. First Let's check the balance of +the two accounts we setup earlier: ``` -basecoin account 0x1B1BE55F969F54064628A63B9559E7C21C925165 -basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 +ME=$(basecli keys get cool | awk '{print $2}') +YOU=$(basecli keys get friend | awk '{print $2}') +basecli query account $ME +basecli query account $YOU ``` The first account is flush with cash, while the second account doesn't exist. Let's send funds from the first account to the second: ``` -basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10mycoin +basecli tx send --name=cool --amount=1000mycoin --to=0x$YOU --sequence=1 ``` -By default, the CLI looks for a `key.json` to sign the transaction with. -To specify a different key, we can use the `--from` flag. - -Now if we check the second account, it should have `10` 'mycoin' coins! +Now if we check the second account, it should have `1000` 'mycoin' coins! ``` -basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 +basecli query account $YOU ``` We can send some of these coins back like so: ``` -basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5mycoin +basecli tx send --name=friend --amount=500mycoin --to=$ME --sequence=1 ``` -Note how we use the `--from` flag to select a different account to send from. +Note how we use the `--name` flag to select a different account to send from. If we try to send too much, we'll get an error: ``` -basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100mycoin +basecli tx send --name=friend --amount=500000mycoin --to=$ME --sequence=1 +``` + +Let's send another transaction: + +``` +basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2 +``` + +Note the `hash` value in the response - this is the hash of the transaction. +We can query for the transaction by this hash: + +``` +basecli query tx ``` -See `basecoin tx send --help` for additional details. +See `basecli tx send --help` for additional details. + +## Proof + +Even if you don't see it in the UI, the result of every query comes with a proof. +This is a Merkle proof that the result of the query is actually contained in the state. +and the state's Merkle root is contained in a recent block header. +Behind the scenes, `countercli` will not only verify that this state matches the header, +but also that the header is properly signed by the known validator set. +It will even update the validator set as needed, so long +as there have not been major changes and it is secure to do so. So, if you wonder +why the query may take a second... there is a lot of work going on in the +background to make sure even a lying full node can't trick your client. + +In a latter [guide on InterBlockchainCommunication](ibc.md), we'll use these +proofs to post transactions to other chains. + +## Accounts and Transactions -For a better understanding of the options, it helps to understand the underlying data structures. +For a better understanding of how to further use the tools, it helps to understand the +underlying data structures. -## Accounts +### Accounts -The Basecoin state consists entirely of a set of accounts. -Each account contains a public key, -a balance in many different coin denominations, -and a strictly increasing sequence number for replay protection. -This type of account was directly inspired by accounts in Ethereum, -and is unlike Bitcoin's use of Unspent Transaction Outputs (UTXOs). -Note Basecoin is a multi-asset cryptocurrency, so each account can have many different kinds of tokens. +The Basecoin state consists entirely of a set of accounts. Each account +contains a public key, a balance in many different coin denominations, and a +strictly increasing sequence number for replay protection. This type of +account was directly inspired by accounts in Ethereum, and is unlike Bitcoin's +use of Unspent Transaction Outputs (UTXOs). Note Basecoin is a multi-asset +cryptocurrency, so each account can have many different kinds of tokens. ```golang type Account struct { @@ -109,17 +179,24 @@ 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). -The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle). +If you want to add more coins to a blockchain, you can do so manually in the `~/.basecoin/genesis.json` before +you start the blockchain for the first time. -## Transactions +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). The +Merkle tree used in Basecoin is a balanced, binary search tree, which we call +an [IAVL tree](https://github.com/tendermint/go-merkle). -Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts. -The `SendTx` takes a list of inputs and a list of outputs, -and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. -The `SendTx` is structured as follows: +### Transactions + +Basecoin defines a simple transaction type, the `SendTx`, which allows tokens +to be sent to other accounts. The `SendTx` takes a list of inputs and a list +of outputs, and transfers all the tokens listed in the inputs from their +corresponding accounts to the accounts listed in the output. The `SendTx` is +structured as follows: ```golang type SendTx struct { @@ -143,32 +220,38 @@ type TxOutput struct { } ``` -Note the `SendTx` includes a field for `Gas` and `Fee`. -The `Gas` limits the total amount of computation that can be done by the transaction, -while the `Fee` refers to the total amount paid in fees. -This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, -where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent, -and the `GasPrice` is implicit. - -In 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... - -Note also that the `PubKey` only needs to be sent for `Sequence == 0`. -After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it, -using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions -as it uses a different elliptic curve scheme which enables the public key to be derived from the signature itself. - -Finally, note that the use of multiple inputs and multiple outputs allows us to send many -different types of tokens between many different accounts at once in an atomic transaction. -Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple -inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of -coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction. +Note the `SendTx` includes a field for `Gas` and `Fee`. The `Gas` limits the +total amount of computation that can be done by the transaction, while the +`Fee` refers to the total amount paid in fees. This is slightly different from +Ethereum's concept of `Gas` and `GasPrice`, where `Fee = Gas x GasPrice`. In +Basecoin, the `Gas` and `Fee` are independent, and the `GasPrice` is implicit. + +In 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... + +Note also that the `PubKey` only needs to be sent for `Sequence == 0`. After +that, it is stored under the account in the Merkle tree and subsequent +transactions can exclude it, using only the `Address` to refer to the sender. +Ethereum does not require public keys to be sent in transactions as it uses a +different elliptic curve scheme which enables the public key to be derived from +the signature itself. + +Finally, note that the use of multiple inputs and multiple outputs allows us to +send many different types of tokens between many different accounts at once in +an atomic transaction. Thus, the `SendTx` can serve as a basic unit of +decentralized exchange. When using multiple inputs and outputs, you must make +sure that the sum of coins of the inputs equals the sum of coins of the outputs +(no creating money), and that all accounts that provide inputs have signed the +transaction. ## Conclusion -In this guide, we introduced the `basecoin` tool, demonstrated how to use it to send tokens between accounts, -and discussed the underlying data types for accounts and transactions, specifically the `Account` and the `SendTx`. -In the [next guide](basecoin-plugins.md), we introduce the basecoin plugin system, which uses a new transaction type, the `AppTx`, -to extend the functionality of the Basecoin system with arbitrary logic. +In this guide, we introduced the `basecoin` and `basecli` tools, +demonstrated how to start a new basecoin blockchain and how to send tokens between accounts, +and discussed the underlying data types for accounts and transactions, specifically the `Account` and the `SendTx`. +In the [next guide](basecoin-plugins.md), we introduce the Basecoin plugin system, +which uses a new transaction type, the `AppTx`, to extend the functionality of +the Basecoin system with arbitrary logic. diff --git a/docs/guide/basecoin-plugins.md b/docs/guide/basecoin-plugins.md index 4e1c2251c14..7e4ac309f33 100644 --- a/docs/guide/basecoin-plugins.md +++ b/docs/guide/basecoin-plugins.md @@ -1,97 +1,115 @@ # Basecoin Plugins -In the [previous guide](basecoin-basics.md), -we saw how to use the `basecoin` tool to start a blockchain and send transactions. -We also learned about `Account` and `SendTx`, the basic data types giving us a multi-asset cryptocurrency. -Here, we will demonstrate how to extend the `basecoin` tool to use another transaction type, the `AppTx`, -to send data to a custom plugin. In this case we use a simple plugin that takes a single boolean argument, -and only accept the transaction if the argument is set to `true`. +In the [previous guide](basecoin-basics.md), we saw how to use the `basecoin` +tool to start a blockchain and the `basecli` tools to send transactions. We also learned about +`Account` and `SendTx`, the basic data types giving us a multi-asset +cryptocurrency. Here, we will demonstrate how to extend the tools to +use another transaction type, the `AppTx`, so we can send data to a custom plugin. In +this example we explore a simple plugin named `counter`. ## Example Plugin -The design of the `basecoin` tool makes it easy to extend for custom functionality. -To see what this looks like, install the `example-plugin` tool: +The design of the `basecoin` tool makes it easy to extend for custom +functionality. The Counter plugin is bundled with basecoin, so if you have +already [installed basecoin](install.md) and run `make install` then you should +be able to run a full node with `counter` and the a light-client `countercli` +from terminal. The Counter plugin is just like the `basecoin` tool. They +both use the same library of commands, including one for signing and +broadcasting `SendTx`. + +Counter transactions take two custom inputs, a boolean argument named `valid`, +and a coin amount named `countfee`. The transaction is only accepted if both +`valid` is set to true and the transaction input coins is greater than +`countfee` that the user provides. + +A new blockchain can be initialized and started just like in the [previous +guide](basecoin-basics.md): ``` -cd $GOPATH/src/github.com/tendermint/basecoin -go install ./docs/guide/src/example-plugin -``` +# WARNING: this wipes out data - but counter is only for demos... +rm -rf ~/.counter +countercli reset_all -The `example-plugin` tool is just like the `basecoin` tool. -They both use the same library of commands, including one for signing and broadcasting `SendTx`. -See `example-plugin --help` for details. +countercli keys new cool +countercli keys new friend -A new blockchain can be initialized and started just like with `basecoin`: +counter init $(countercli keys get cool | awk '{print $2}') -``` -example-plugin init -example-plugin start +counter start ``` -The default files are stored in `~/.basecoin-example-plugin`. -In another window, we can send a `SendTx` like we are used to: +The default files are stored in `~/.counter`. In another window we can +initialize the light-client and send a transaction: ``` -example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 1mycoin +countercli init --chain-id=test_chain_id --node=tcp://localhost:46657 + +YOU=$(countercli keys get friend | awk '{print $2}') +countercli tx send --name=cool --amount=1000mycoin --to=0x$YOU --sequence=1 ``` -But the `example-plugin` tool has an additional command, `example-plugin tx example`, -which crafts an `AppTx` specifically for our example plugin. -This command lets you send a single boolean argument: +But the Counter has an additional command, `countercli tx counter`, which +crafts an `AppTx` specifically for this plugin: ``` -example-plugin tx example --amount 1mycoin -example-plugin tx example --amount 1mycoin --valid +countercli tx counter --name cool --amount=1mycoin --sequence=2 +countercli tx counter --name cool --amount=1mycoin --sequence=3 --valid ``` -The first transaction is rejected by the plugin because it was not marked as valid, while the second transaction passes. -We can build plugins that take many arguments of different types, and easily extend the tool to accomodate them. -Of course, we can also expose queries on our plugin: +The first transaction is rejected by the plugin because it was not marked as +valid, while the second transaction passes. We can build plugins that take +many arguments of different types, and easily extend the tool to accomodate +them. Of course, we can also expose queries on our plugin: ``` -example-plugin query ExamplePlugin.State +countercli query counter ``` -Note the `"value":"0101"`. This is the serialized form of the state, -which contains only an integer, the number of valid transactions. -If we send another transaction, and then query again, we will see the value increment: +Tada! We can now see that our custom counter plugin tx went through. You +should see a Counter value of 1 representing the number of valid transactions. +If we send another transaction, and then query again, we will see the value +increment: ``` -example-plugin tx example --valid --amount 1mycoin -example-plugin query ExamplePlugin.State +countercli tx counter --name cool --amount=2mycoin --sequence=4 --valid --countfee=2mycoin +countercli query counter ``` -The value should now be `0102`, because we sent a second valid transaction. -Notice how the result of the query comes with a proof. -This is a Merkle proof that the state is what we say it is. -In a latter [guide on InterBlockchain Communication](ibc.md), -we'll put this proof to work! +The Counter value should be 2, because we sent a second valid transaction. +And this time, since we sent a countfee (which must be less than or equal to the +total amount sent with the tx), it stores the `TotalFees` on the counter as well. +Keep it mind that, just like with `basecli`, the `countercli` verifies a proof +that the query response is correct and up-to-date. -Now, before we implement our own plugin and tooling, it helps to understand the `AppTx` and the design of the plugin system. +Now, before we implement our own plugin and tooling, it helps to understand the +`AppTx` and the design of the plugin system. ## AppTx -The `AppTx` is similar to the `SendTx`, but instead of sending coins from inputs to outputs, -it sends coins from one input to a plugin, and can also send some data. +The `AppTx` is similar to the `SendTx`, but instead of sending coins from +inputs to outputs, it sends coins from one input to a plugin, and can also send +some data. ```golang type AppTx struct { - Gas int64 `json:"gas"` - Fee Coin `json:"fee"` + Gas int64 `json:"gas"` + Fee Coin `json:"fee"` Input TxInput `json:"input"` Name string `json:"type"` // Name of the plugin Data []byte `json:"data"` // Data for the plugin to process } ``` -The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins. -The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction, -and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing. +The `AppTx` enables Basecoin to be extended with arbitrary additional +functionality through the use of plugins. The `Name` field in the `AppTx` +refers to the particular plugin which should process the transaction, and the +`Data` field of the `AppTx` is the data to be forwarded to the plugin for +processing. -Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`. -It also includes a single `TxInput`, which specifies the sender of the transaction, -and some coins that can be forwarded to the plugin as well. +Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the +`SendTx`. It also includes a single `TxInput`, which specifies the sender of +the transaction, and some coins that can be forwarded to the plugin as well. ## Plugins @@ -120,43 +138,59 @@ type CallContext struct { } ``` -The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed. -The `Data` from the `AppTx` is passed in as the `txBytes`, -while the `Input` from the `AppTx` is used to populate the `CallContext`. +The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is +processed. The `Data` from the `AppTx` is passed in as the `txBytes`, while +the `Input` from the `AppTx` is used to populate the `CallContext`. -Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data. -By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly, -and also to store arbitrary other information in the state. -In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended. -One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! +Note that `RunTx` also takes a `KVStore` - this is an abstraction for the +underlying Merkle tree which stores the account data. By passing this to the +plugin, we enable plugins to update accounts in the Basecoin state directly, +and also to store arbitrary other information in the state. In this way, the +functionality and state of a Basecoin-derived cryptocurrency can be greatly +extended. One could imagine going so far as to implement the Ethereum Virtual +Machine as a plugin! -For details on how to initialize the state using `SetOption`, see the [guide to using the basecoin tool](basecoin-tool.md#genesis). +For details on how to initialize the state using `SetOption`, see the [guide to +using the basecoin tool](basecoin-tool.md#genesis). ## Implement your own -To implement your own plugin and tooling, make a copy of `docs/guide/src/example-plugin`, -and modify the code accordingly. Here, we will briefly describe the design and the changes to be made, -but see the code for more details. +To implement your own plugin and tooling, make a copy of +`docs/guide/counter`, and modify the code accordingly. Here, we will +briefly describe the design and the changes to be made, but see the code for +more details. + +First is the `cmd/counter/main.go`, which drives the program. It can be left +alone, but you should change any occurrences of `counter` to whatever your +plugin tool is going to be called. You must also register your plugin(s) with +the basecoin app with `RegisterStartPlugin`. -First is the `main.go`, which drives the program. It can be left alone, but you should change any occurences of `example-plugin` -to whatever your plugin tool is going to be called. +The light-client is located in `cmd/countercli/main.go` and allows for +transaction and query commands. This file can also be left mostly alone besides replacing the application name and adding +references to new plugin commands. -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`. +Next is the custom commands in `cmd/countercli/commands/`. These files are +where we extend the tool with any new commands and flags we need to send +transactions or queries to our plugin. You define custom `tx` and `query` +subcommands, which are registered in `main.go` (avoiding `init()` +auto-registration, for less magic and more control in the main executable). -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 -sent along in the `AppTx`. In our example, we define a new transaction type, the `ExamplePluginTx`, which -we expect to be encoded in the `AppTx.Data`, and thus to be decoded in the `RunTx` method, and used to update the plugin state. +Finally is `plugins/counter/counter.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 sent along in the +`AppTx`. In our example, we define a new transaction type, the `CounterTx`, +which we expect to be encoded in the `AppTx.Data`, and thus to be decoded in +the `RunTx` method, and used to update the plugin state. -For more examples and inspiration, see our [repository of example plugins](https://github.com/tendermint/basecoin-examples). +For more examples and inspiration, see our [repository of example +plugins](https://github.com/tendermint/basecoin-examples). ## Conclusion In this guide, we demonstrated how to create a new plugin and how to extend the -`basecoin` tool to start a blockchain with the plugin enabled and send transactions to it. -In the next guide, we introduce a [plugin for Inter Blockchain Communication](ibc.md), -which allows us to publish proofs of the state of one blockchain to another, -and thus to transfer tokens and data between them. +`basecoin` tool to start a blockchain with the plugin enabled and send +transactions to it. In the next guide, we introduce a [plugin for Inter +Blockchain Communication](ibc.md), which allows us to publish proofs of the +state of one blockchain to another, and thus to transfer tokens and data +between them. diff --git a/docs/guide/basecoin-tool.md b/docs/guide/basecoin-tool.md index 53d8df03780..eb35ec9ea1a 100644 --- a/docs/guide/basecoin-tool.md +++ b/docs/guide/basecoin-tool.md @@ -1,37 +1,49 @@ # The Basecoin Tool -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. +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. + +# Generate a Key + +Generate a key using the `basecli` tool: + +``` +basecli keys new mykey +ME=$(basecli keys get mykey | awk '{print $2}') +``` # Data Directory -By default, `basecoin` works out of `~/.basecoin`. To change this, set the `BCHOME` environment variable: +By default, `basecoin` works out of `~/.basecoin`. To change this, set the +`BCHOME` environment variable: ``` export BCHOME=~/.my_basecoin_data -basecoin init +basecoin init $ME basecoin start ``` or ``` -BCHOME=~/.my_basecoin_data basecoin init +BCHOME=~/.my_basecoin_data basecoin init $ME BCHOME=~/.my_basecoin_data basecoin start ``` # ABCI Server -So far we have run Basecoin and Tendermint in a single process. -However, since we use ABCI, we can actually run them in different processes. -First, initialize them: +So far we have run Basecoin and Tendermint in a single process. However, since +we use ABCI, we can actually run them in different processes. First, +initialize them: ``` -basecoin init +basecoin init $ME ``` -This will create a single `genesis.json` file in `~/.basecoin` with the information for both Basecoin and Tendermint. +This will create a single `genesis.json` file in `~/.basecoin` with the +information for both Basecoin and Tendermint. Now, In one window, run @@ -47,7 +59,8 @@ TMROOT=~/.basecoin tendermint node You should see Tendermint start making blocks! -Alternatively, you could ignore the Tendermint details in `~/.basecoin/genesis.json` and use a separate directory by running: +Alternatively, you could ignore the Tendermint details in +`~/.basecoin/genesis.json` and use a separate directory by running: ``` tendermint init @@ -58,9 +71,11 @@ For more details on using `tendermint`, see [the guide](https://tendermint.com/d # Keys and Genesis -In previous tutorials we used `basecoin init` to initialize `~/.basecoin` with the default configuration. -This command creates files both for Tendermint and for Basecoin, and a single `genesis.json` file for both of them. -For more information on these files, see the [guide to using tendermint](https://tendermint.com/docs/guides/using-tendermint). +In previous tutorials we used `basecoin init` to initialize `~/.basecoin` with +the default configuration. This command creates files both for Tendermint and +for Basecoin, and a single `genesis.json` file for both of them. For more +information on these files, see the [guide to using +Tendermint](https://tendermint.com/docs/guides/using-tendermint). Now let's make our own custom Basecoin data. @@ -70,67 +85,88 @@ First, create a new directory: mkdir example-data ``` -We can tell `basecoin` to use this directory by exporting the `BCHOME` environment variable: +We can tell `basecoin` to use this directory by exporting the `BCHOME` +environment variable: ``` export BCHOME=$(pwd)/example-data ``` -If you're going to be using multiple terminal windows, make sure to add this variable to your shell startup scripts (eg. `~/.bashrc`). +If you're going to be using multiple terminal windows, make sure to add this +variable to your shell startup scripts (eg. `~/.bashrc`). -Now, let's create a new private key: +Now, let's create a new key: ``` -basecoin key new > $BCHOME/key.json +basecli keys new foobar ``` -Here's what my `key.json looks like: +The key's info can be retrieved with + +``` +basecli keys get foobar -o=json +``` + +You should get output which looks similar to the following: ```json { - "address": "4EGEhnqOw/gX326c7KARUkY1kic=", - "pub_key": { - "type": "ed25519", - "data": "a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141" - }, - "priv_key": { - "type": "ed25519", - "data": "654c845f4b36d1a881deb0ff09381165d3ccd156b4aabb5b51267e91f1d024a5a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141" - } + "name": "foobar", + "address": "404C5003A703C7DA888C96A2E901FCE65A6869D9", + "pubkey": { + "type": "ed25519", + "data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA" + } } ``` -Yours will look different - each key is randomly derrived. - -Now we can make a `genesis.json` file and add an account with our public key: +Yours will look different - each key is randomly derived. Now we can make a +`genesis.json` file and add an account with our public key: ```json { + "app_hash": "", "chain_id": "example-chain", - "app_options": { - "accounts": [{ + "genesis_time": "0001-01-01T00:00:00.000Z", + "validators": [ + { + "amount": 10, + "name": "", "pub_key": { "type": "ed25519", - "data": "a20d48b5caff42892d0ac67ccdeee38c1dcbbe42b15b486057d16244541e8141" - }, - "coins": [ - { - "denom": "gold", - "amount": 1000000000 - } - ] - }] + "data": "7B90EA87E7DC0C7145C8C48C08992BE271C7234134343E8A8E8008E617DE7B30" + } + } + ], + "app_options": { + "accounts": [ + { + "pub_key": { + "type": "ed25519", + "data": "8786B7812AB3B27892D8E14505EEFDBB609699E936F6A4871B1983F210736EEA" + }, + "coins": [ + { + "denom": "gold", + "amount": 1000000000 + } + ] + } + ] } } ``` -Here we've granted ourselves `1000000000` units of the `gold` token. -Note that we've also set the `chain_id` to be `example-chain`. -All transactions must therefore include the `--chain_id example-chain` in order to make sure they are valid for this chain. -Previously, we didn't need this flag because we were using the default chain ID ("test_chain_id"). -Now that we're using a custom chain, we need to specify the chain explicitly on the command line. +Here we've granted ourselves `1000000000` units of the `gold` token. Note that +we've also set the `chain-id` to be `example-chain`. All transactions must +therefore include the `--chain-id example-chain` in order to make sure they are +valid for this chain. Previously, we didn't need this flag because we were +using the default chain ID ("test_chain_id"). Now that we're using a custom +chain, we need to specify the chain explicitly on the command line. -Note we have also left out the details of the tendermint genesis. These are documented in the [tendermint guide](https://tendermint.com/docs/guides/using-tendermint). +Note we have also left out the details of the Tendermint genesis. These are +documented in the [Tendermint +guide](https://tendermint.com/docs/guides/using-tendermint). # Reset @@ -141,13 +177,19 @@ You can reset all blockchain data by running: basecoin unsafe_reset_all ``` +Similarly, you can reset client data by running: + +``` +basecli reset_all +``` # Genesis -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 -as the plugin state. +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 as the plugin state. diff --git a/cmd/counter/main.go b/docs/guide/counter/cmd/counter/main.go similarity index 50% rename from cmd/counter/main.go rename to docs/guide/counter/cmd/counter/main.go index 083535733f3..c877a51eda8 100644 --- a/cmd/counter/main.go +++ b/docs/guide/counter/cmd/counter/main.go @@ -5,8 +5,11 @@ import ( "github.com/spf13/cobra" - "github.com/tendermint/basecoin/cmd/commands" "github.com/tendermint/tmlibs/cli" + + "github.com/tendermint/basecoin/cmd/basecoin/commands" + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" + "github.com/tendermint/basecoin/types" ) func main() { @@ -18,16 +21,11 @@ func main() { RootCmd.AddCommand( commands.InitCmd, commands.StartCmd, - commands.TxCmd, - commands.QueryCmd, - commands.KeyCmd, - commands.VerifyCmd, - commands.BlockCmd, - commands.AccountCmd, commands.UnsafeResetAllCmd, commands.VersionCmd, ) - cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin")) + commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) + cmd := cli.PrepareMainCmd(RootCmd, "CT", os.ExpandEnv("$HOME/.counter")) cmd.Execute() } diff --git a/docs/guide/counter/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go new file mode 100644 index 00000000000..0481e3d0654 --- /dev/null +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -0,0 +1,83 @@ +package commands + +import ( + "github.com/spf13/cobra" + "github.com/spf13/viper" + + wire "github.com/tendermint/go-wire" + txcmd "github.com/tendermint/light-client/commands/txs" + + bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" + btypes "github.com/tendermint/basecoin/types" +) + +//CounterTxCmd is the CLI command to execute the counter +// through the appTx Command +var CounterTxCmd = &cobra.Command{ + Use: "counter", + Short: "add a vote to the counter", + Long: `Add a vote to the counter. + +You must pass --valid for it to count and the countfee will be added to the counter.`, + RunE: counterTxCmd, +} + +const ( + flagCountFee = "countfee" + flagValid = "valid" +) + +func init() { + fs := CounterTxCmd.Flags() + bcmd.AddAppTxFlags(fs) + fs.String(flagCountFee, "", "Coins to send in the format ,...") + fs.Bool(flagValid, false, "Is count valid?") +} + +func counterTxCmd(cmd *cobra.Command, args []string) error { + // Note: we don't support loading apptx from json currently, so skip that + + // Read the app-specific flags + name, data, err := getAppData() + if err != nil { + return err + } + + // Read the standard app-tx flags + gas, fee, txInput, err := bcmd.ReadAppTxFlags() + if err != nil { + return err + } + + // Create AppTx and broadcast + tx := &btypes.AppTx{ + Gas: gas, + Fee: fee, + Name: name, + Input: txInput, + Data: data, + } + res, err := bcmd.BroadcastAppTx(tx) + if err != nil { + return err + } + + // Output result + return txcmd.OutputTx(res) +} + +func getAppData() (name string, data []byte, err error) { + countFee, err := btypes.ParseCoins(viper.GetString(flagCountFee)) + if err != nil { + return + } + ctx := counter.CounterTx{ + Valid: viper.GetBool(flagValid), + Fee: countFee, + } + + name = counter.New().Name() + data = wire.BinaryBytes(ctx) + return +} diff --git a/docs/guide/counter/cmd/countercli/commands/query.go b/docs/guide/counter/cmd/countercli/commands/query.go new file mode 100644 index 00000000000..692e04c7b3f --- /dev/null +++ b/docs/guide/counter/cmd/countercli/commands/query.go @@ -0,0 +1,28 @@ +package commands + +import ( + "github.com/spf13/cobra" + + proofcmd "github.com/tendermint/light-client/commands/proofs" + + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" +) + +//CounterQueryCmd CLI command to query the counter state +var CounterQueryCmd = &cobra.Command{ + Use: "counter", + Short: "Query counter state, with proof", + RunE: counterQueryCmd, +} + +func counterQueryCmd(cmd *cobra.Command, args []string) error { + key := counter.New().StateKey() + + var cp counter.CounterPluginState + proof, err := proofcmd.GetAndParseAppProof(key, &cp) + if err != nil { + return err + } + + return proofcmd.OutputProof(cp, proof.BlockHeight()) +} diff --git a/docs/guide/counter/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go new file mode 100644 index 00000000000..7b543d35266 --- /dev/null +++ b/docs/guide/counter/cmd/countercli/main.go @@ -0,0 +1,69 @@ +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/docs/guide/counter/cmd/countercli/commands" +) + +// BaseCli represents the base command when called without any subcommands +var BaseCli = &cobra.Command{ + Use: "countercli", + 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) + + // Prepare queries + proofs.RootCmd.AddCommand( + // These are default parsers, optional in your app + proofs.TxCmd, + proofs.KeyCmd, + bcmd.AccountQueryCmd, + + // XXX IMPORTANT: here is how you add custom query commands in your app + bcount.CounterQueryCmd, + ) + + // Prepare transactions + proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) + txs.RootCmd.AddCommand( + // This is the default transaction, optional in your app + bcmd.SendTxCmd, + + // XXX IMPORTANT: here is how you add custom tx construction for your app + bcount.CounterTxCmd, + ) + + // Set up the various commands to use + BaseCli.AddCommand( + commands.InitCmd, + commands.ResetCmd, + keycmd.RootCmd, + seeds.RootCmd, + proofs.RootCmd, + txs.RootCmd, + proxy.RootCmd, + ) + + cmd := cli.PrepareMainCmd(BaseCli, "CTL", os.ExpandEnv("$HOME/.countercli")) + cmd.Execute() +} diff --git a/plugins/counter/counter.go b/docs/guide/counter/plugins/counter/counter.go similarity index 100% rename from plugins/counter/counter.go rename to docs/guide/counter/plugins/counter/counter.go diff --git a/plugins/counter/counter_test.go b/docs/guide/counter/plugins/counter/counter_test.go similarity index 100% rename from plugins/counter/counter_test.go rename to docs/guide/counter/plugins/counter/counter_test.go diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 71e196d01bd..8036a84cb68 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -1,29 +1,28 @@ # InterBlockchain Communication with Basecoin -One of the most exciting elements of the Cosmos Network is the InterBlockchain Communication (IBC) protocol, -which enables interoperability across different blockchains. -The simplest example of using the IBC protocol is to send a data packet from one blockchain to another. +One of the most exciting elements of the Cosmos Network is the InterBlockchain +Communication (IBC) protocol, which enables interoperability across different +blockchains. We implemented IBC as a basecoin plugin, and we'll show you +how to use it to send tokens across blockchains! -We implemented IBC as a basecoin plugin. -and here we'll show you how to use the Basecoin IBC-plugin to send a packet of data across blockchains! - -Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/basecoin-plugins.md), -but we'll explain how IBC works. You may also want to see [our repository of example plugins](https://github.com/tendermint/basecoin-examples). +Please note, this tutorial assumes you are familiar with [Basecoin +plugins](/docs/guide/basecoin-plugins.md), but we'll explain how IBC works. You +may also want to see [our repository of example +plugins](https://github.com/tendermint/basecoin-examples). The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. -The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, -and setting the `Data` field to the serialized IBC transaction type. +The plugin's functionality is accessed by setting the `AppTx.Name` field to +`"IBC"`, and setting the `Data` field to the serialized IBC transaction type. We'll demonstrate exactly how this works below. ## IBC -Let's review the IBC protocol. -The purpose of IBC is to enable one blockchain to function as a light-client of another. -Since we are using a classical Byzantine Fault Tolerant consensus algorithm, -light-client verification is cheap and easy: -all we have to do is check validator signatures on the latest block, -and verify a Merkle proof of the state. +Let's review the IBC protocol. The purpose of IBC is to enable one blockchain +to function as a light-client of another. Since we are using a classical +Byzantine Fault Tolerant consensus algorithm, light-client verification is +cheap and easy: all we have to do is check validator signatures on the latest +block, and verify a Merkle proof of the state. In Tendermint, validators agree on a block before processing it. This means that the signatures and state root for that block aren't included until the @@ -31,9 +30,9 @@ next block. Thus, each block contains a field called `LastCommit`, which contains the votes responsible for committing the previous block, and a field in the block header called `AppHash`, which refers to the Merkle root hash of the application after processing the transactions from the previous block. So, -if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit` -at height H+1. (And remember that this `AppHash` only contains the results from all -transactions up to and including block H-1) +if we want to verify the `AppHash` from height H, we need the signatures from +`LastCommit` at height H+1. (And remember that this `AppHash` only contains the +results from all transactions up to and including block H-1) Unlike Proof-of-Work, the light-client protocol does not need to download and check all the headers in the blockchain - the client can always jump straight @@ -43,109 +42,112 @@ changes, which requires downloading headers for each block in which there is a significant change. Here, we will assume the validator set is constant, and postpone handling validator set changes for another time. -Now we can describe exactly how IBC works. -Suppose we have two blockchains, `chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`. +Now we can describe exactly how IBC works. Suppose we have two blockchains, +`chain1` and `chain2`, and we want to send some data from `chain1` to `chain2`. We need to do the following: - 1. Register the details (ie. chain ID and genesis configuration) of `chain1` on `chain2` - 2. Within `chain1`, broadcast a transaction that creates an outgoing IBC packet destined for `chain2` - 3. Broadcast a transaction to `chain2` informing it of the latest state (ie. header and commit signatures) of `chain1` - 4. Post the outgoing packet from `chain1` to `chain2`, including the proof that -it was indeed committed on `chain1`. Note `chain2` can only verify this proof -because it has a recent header and commit. - -Each of these steps involves a separate IBC transaction type. Let's take them up in turn. + 1. Register the details (ie. chain ID and genesis configuration) of `chain1` + on `chain2` + 2. Within `chain1`, broadcast a transaction that creates an outgoing IBC + packet destined for `chain2` + 3. Broadcast a transaction to `chain2` informing it of the latest state (ie. + header and commit signatures) of `chain1` + 4. Post the outgoing packet from `chain1` to `chain2`, including the proof + that it was indeed committed on `chain1`. Note `chain2` can only verify +this proof because it has a recent header and commit. + +Each of these steps involves a separate IBC transaction type. Let's take them +up in turn. ### IBCRegisterChainTx -The `IBCRegisterChainTx` is used to register one chain on another. -It contains the chain ID and genesis configuration of the chain to register: +The `IBCRegisterChainTx` is used to register one chain on another. It contains +the chain ID and genesis configuration of the chain to register: ```golang -type IBCRegisterChainTx struct { - BlockchainGenesis -} +type IBCRegisterChainTx struct { BlockchainGenesis } -type BlockchainGenesis struct { - ChainID string - Genesis string -} +type BlockchainGenesis struct { ChainID string Genesis string } ``` -This transaction should only be sent once for a given chain ID, and successive sends will return an error. +This transaction should only be sent once for a given chain ID, and successive +sends will return an error. ### IBCUpdateChainTx -The `IBCUpdateChainTx` is used to update the state of one chain on another. -It contains the header and commit signatures for some block in the chain: +The `IBCUpdateChainTx` is used to update the state of one chain on another. It +contains the header and commit signatures for some block in the chain: ```golang type IBCUpdateChainTx struct { - Header tm.Header - Commit tm.Commit + Header tm.Header + Commit tm.Commit } ``` -In the future, it needs to be updated to include changes to the validator set as well. -Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequently as packets are being sent or the validator set is changing. +In the future, it needs to be updated to include changes to the validator set +as well. Anyone can relay an `IBCUpdateChainTx`, and they only need to do so +as frequently as packets are being sent or the validator set is changing. ### IBCPacketCreateTx -The `IBCPacketCreateTx` is used to create an outgoing packet on one chain. -The packet itself contains the source and destination chain IDs, -a sequence number (i.e. an integer that increments with every message sent between this pair of chains), -a packet type (e.g. coin, data, etc.), -and a payload. +The `IBCPacketCreateTx` is used to create an outgoing packet on one chain. The +packet itself contains the source and destination chain IDs, a sequence number +(i.e. an integer that increments with every message sent between this pair of +chains), a packet type (e.g. coin, data, etc.), and a payload. ```golang type IBCPacketCreateTx struct { - Packet + Packet } type Packet struct { - SrcChainID string - DstChainID string - Sequence uint64 - Type string - Payload []byte + SrcChainID string + DstChainID string + Sequence uint64 + Type string + Payload []byte } ``` -We have yet to define the format for the payload, so, for now, it's just arbitrary bytes. +We have yet to define the format for the payload, so, for now, it's just +arbitrary bytes. -One way to think about this is that `chain2` has an account on `chain1`. -With a `IBCPacketCreateTx` on `chain1`, we send funds to that account. -Then we can prove to `chain2` that there are funds locked up for it in it's -account on `chain1`. -Those funds can only be unlocked with corresponding IBC messages back from -`chain2` to `chain1` sending the locked funds to another account on +One way to think about this is that `chain2` has an account on `chain1`. With +a `IBCPacketCreateTx` on `chain1`, we send funds to that account. Then we can +prove to `chain2` that there are funds locked up for it in it's account on +`chain1`. Those funds can only be unlocked with corresponding IBC messages +back from `chain2` to `chain1` sending the locked funds to another account on `chain1`. ### IBCPacketPostTx -The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another. -It contains the packet and a proof that the packet was committed into the state of the sending chain: +The `IBCPacketPostTx` is used to post an outgoing packet from one chain to +another. It contains the packet and a proof that the packet was committed into +the state of the sending chain: ```golang type IBCPacketPostTx struct { - FromChainID string // The immediate source of the packet, not always Packet.SrcChainID - FromChainHeight uint64 // The block height in which Packet was committed, to check Proof - Packet - Proof *merkle.IAVLProof + FromChainID string // The immediate source of the packet, not always Packet.SrcChainID + FromChainHeight uint64 // The block height in which Packet was committed, to check Proof Packet + Proof *merkle.IAVLProof } ``` -The proof is a Merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree. -It contains a list of nodes in the tree, which can be hashed together to get the Merkle root hash. -This hash must match the `AppHash` contained in the header at `FromChainHeight + 1` -- note the `+ 1` is necessary since `FromChainHeight` is the height in which the packet was committed, -and the resulting state root is not included until the next block. +The proof is a Merkle proof in an IAVL tree, our implementation of a balanced, +Merklized binary search tree. It contains a list of nodes in the tree, which +can be hashed together to get the Merkle root hash. This hash must match the +`AppHash` contained in the header at `FromChainHeight + 1` + +- note the `+ 1` is necessary since `FromChainHeight` is the height in which + the packet was committed, and the resulting state root is not included until +the next block. ### IBC State Now that we've seen all the transaction types, let's talk about the state. -Each chain stores some IBC state in its Merkle tree. -For each chain being tracked by our chain, we store: +Each chain stores some IBC state in its Merkle tree. For each chain being +tracked by our chain, we store: - Genesis configuration - Latest state @@ -154,167 +156,239 @@ For each chain being tracked by our chain, we store: We also store all incoming (ingress) and outgoing (egress) packets. The state of a chain is updated every time an `IBCUpdateChainTx` is committed. -New packets are added to the egress state upon `IBCPacketCreateTx`. -New packets are added to the ingress state upon `IBCPacketPostTx`, -assuming the proof checks out. +New packets are added to the egress state upon `IBCPacketCreateTx`. New +packets are added to the ingress state upon `IBCPacketPostTx`, assuming the +proof checks out. ## Merkle Queries -The Basecoin application uses a single Merkle tree that is shared across all its state, -including the built-in accounts state and all plugin state. For this reason, -it's important to use explicit key names and/or hashes to ensure there are no collisions. +The Basecoin application uses a single Merkle tree that is shared across all +its state, including the built-in accounts state and all plugin state. For this +reason, it's important to use explicit key names and/or hashes to ensure there +are no collisions. -We can query the Merkle tree using the ABCI Query method. -If we pass in the correct key, it will return the corresponding value, -as well as a proof that the key and value are contained in the Merkle tree. +We can query the Merkle tree using the ABCI Query method. If we pass in the +correct key, it will return the corresponding value, as well as a proof that +the key and value are contained in the Merkle tree. The results of a query can thus be used as proof in an `IBCPacketPostTx`. +## Relay + +While we need all these packet types internally to keep track of all the +proofs on both chains in a secure manner, for the normal work-flow, +we can run a relay node that handles the cross-chain interaction. + +In this case, there are only two steps. First `basecoin relay init`, +which must be run once to register each chain with the other one, +and make sure they are ready to send and recieve. And then +`basecoin relay start`, which is a long-running process polling the queue +on each side, and relaying all new message to the other block. + +This requires that the relay has access to accounts with some funds on both +chains to pay for all the ibc packets it will be forwarding. + ## Try it out -Now that we have all the background knowledge, let's actually walk through the tutorial. +Now that we have all the background knowledge, let's actually walk through the +tutorial. + +Make sure you have installed [basecoin and basecli](/docs/guide/install.md). -Make sure you have installed -[Tendermint](https://tendermint.com/intro/getting-started/download) and -[basecoin](/docs/guide/install.md). +Basecoin is a framework for creating new cryptocurrency applications. It +comes with an `IBC` plugin enabled by default. -`basecoin` is a framework for creating new cryptocurrency applications. -It comes with an `IBC` plugin enabled by default. +You will also want to install the [jq](https://stedolan.github.io/jq/) for +handling JSON at the command line. -You will also want to install the [jq](https://stedolan.github.io/jq/) for handling JSON at the command line. +If you have any trouble with this, you can also look at the +[test scripts](/tests/cli/ibc.sh) or just run `make test_cli` in basecoin repo. +Otherwise, open up 5 (yes 5!) terminal tabs.... -Now let's start the two blockchains. -In this tutorial, each chain will have only a single validator, -where the initial configuration files are already generated. -Let's change directory so these files are easily accessible: +### Preliminaries ``` -cd $GOPATH/src/github.com/tendermint/basecoin/demo +# first, clean up any old garbage for a fresh slate... +rm -rf ~/.ibcdemo/ ``` -The relevant data is now in the `data` directory. -Before we begin, let's set some environment variables for convenience: +Let's start by setting up some environment variables and aliases: ``` -export BCHOME="." -BCHOME1="./data/chain1" -BCHOME2="./data/chain2" +export BCHOME1_CLIENT=~/.ibcdemo/chain1/client +export BCHOME1_SERVER=~/.ibcdemo/chain1/server +export BCHOME2_CLIENT=~/.ibcdemo/chain2/client +export BCHOME2_SERVER=~/.ibcdemo/chain2/server +alias basecli1="basecli --home $BCHOME1_CLIENT" +alias basecli2="basecli --home $BCHOME2_CLIENT" +alias basecoin1="basecoin --home $BCHOME1_SERVER" +alias basecoin2="basecoin --home $BCHOME2_SERVER" +``` + +This will give us some new commands to use instead of raw `basecli` and `basecoin` to ensure we're using the right configuration for the chain we want to talk to. -export CHAIN_ID1=test_chain_1 -export CHAIN_ID2=test_chain_2 +We also want to set some chain IDs: -CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json" -CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657" +``` +export CHAINID1="test-chain-1" +export CHAINID2="test-chain-2" ``` -In previous examples, we started basecoin in-process with tendermint. -Here, we will run them in different processes, using the `--without-tendermint` flag, -as described in the [guide to the basecoin tool](basecoin-tool.md). -We can start the two chains as follows: +And since we will run two different chains on one machine, we need to maintain different sets of ports: ``` -TMROOT=$BCHOME1 tendermint node --log_level=info &> chain1_tendermint.log & -BCHOME=$BCHOME1 basecoin start --without-tendermint &> chain1_basecoin.log & +export PORT_PREFIX1=1234 +export PORT_PREFIX2=2345 +export RPC_PORT1=${PORT_PREFIX1}7 +export RPC_PORT2=${PORT_PREFIX2}7 ``` -and +### Setup Chain 1 + +Now, let's create some keys that we can use for accounts on test-chain-1: ``` -TMROOT=$BCHOME2 tendermint node --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> chain2_basecoin.log & +basecli1 keys new money +basecli1 keys new gotnone +export MONEY=$(basecli1 keys get money | awk '{print $2}') +export GOTNONE=$(basecli1 keys get gotnone | awk '{print $2}') ``` -Note how we refer to the relevant data directories, and how we set the various addresses for the second node so as not to conflict with the first. +and create an initial configuration giving lots of coins to the $MONEY key: -We can now check on the status of the two chains: +``` +basecoin1 init --chain-id $CHAINID1 $MONEY +``` + +Now start basecoin: ``` -curl localhost:46657/status -curl localhost:36657/status +sed -ie "s/4665/$PORT_PREFIX1/" $BCHOME1_SERVER/config.toml + +basecoin1 start &> basecoin1.log & ``` -If either command fails, the nodes may not have finished starting up. Wait a couple seconds and try again. -Once you see the status of both chains, it's time to move on. +Note the `sed` command to replace the ports in the config file. +You can follow the logs with `tail -f basecoin1.log` -In this tutorial, we're going to send some data from `test_chain_1` to `test_chain_2`. -We begin by registering `test_chain_1` on `test_chain_2`: +Now we can attach the client to the chain and verify the state. +The first account should have money, the second none: ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json +basecli1 init --chain-id=$CHAINID1 --node=tcp://localhost:${RPC_PORT1} +basecli1 query account $MONEY +basecli1 query account $GOTNONE ``` -Now we can create the outgoing packet on `test_chain_1`: +### Setup Chain 2 + +This is the same as above, except with `basecli2`, `basecoin2`, and `$CHAINID2`. +We will also need to change the ports, since we're running another chain on the same local machine. + +Let's create new keys for test-chain-2: ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --ibc_sequence 1 +basecli2 keys new moremoney +basecli2 keys new broke +MOREMONEY=$(basecli2 keys get moremoney | awk '{print $2}') +BROKE=$(basecli2 keys get broke | awk '{print $2}') ``` -Note our payload is just `DEADBEEF`. -Now that the packet is committed in the chain, let's get some proof by querying: +And prepare the genesis block, and start the server: ``` -QUERY=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) -echo $QUERY -``` +basecoin2 init --chain-id $CHAINID2 $(basecli2 keys get moremoney | awk '{print $2}') + +sed -ie "s/4665/$PORT_PREFIX2/" $BCHOME2_SERVER/config.toml -The result contains the latest height, a value (i.e. the hex-encoded binary serialization of our packet), -and a proof (i.e. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree. -We keep the result in the `QUERY` variable so we can easily reference subfields using the `jq` tool. +basecoin2 start &> basecoin2.log & +``` -If we want to send this data to `test_chain_2`, we first have to update what it knows about `test_chain_1`. -We'll need a recent block header and a set of commit signatures. -Fortunately, we can get them with the `block` command: +Now attach the client to the chain and verify the state. +The first account should have money, the second none: ``` -BLOCK=$(basecoin block $(echo $QUERY | jq .height)) -echo $BLOCK +basecli2 init --chain-id=$CHAINID2 --node=tcp://localhost:${RPC_PORT2} +basecli2 query account $MOREMONEY +basecli2 query account $BROKE ``` -Here, we are passing `basecoin block` the `height` from our earlier query. -Note the result contains both a hex-encoded and json-encoded version of the header and the commit. -The former is used as input for later commands; the latter is human-readable, so you know what's going on! +### Connect these chains + +OK! So we have two chains running on your local machine, with different +keys on each. Let's hook them up together by starting a relay process to +forward messages from one chain to the other. -Let's send this updated information about `test_chain_1` to `test_chain_2`. -First, output the header and commit for reference: +The relay account needs some money in it to pay for the ibc messages, so +for now, we have to transfer some cash from the rich accounts before we start +the actual relay. ``` -echo $BLOCK | jq .hex.header -echo $BLOCK | jq .hex.commit +# note that this key.json file is a hardcoded demo for all chains, this will +# be updated in a future release +RELAY_KEY=$BCHOME1_SERVER/key.json +RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") + +basecli1 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR--name=money +basecli1 query account $RELAY_ADDR + +basecli2 tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney +basecli2 query account $RELAY_ADDR ``` -And now forward those values to `test_chain_2`: +Now we can start the relay process. ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x
--commit 0x +basecoin relay init --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \ + --chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \ + --genesis1=${BCHOME1_SERVER}/genesis.json --genesis2=${BCHOME2_SERVER}/genesis.json \ + --from=$RELAY_KEY + +basecoin relay start --chain1-id=$CHAINID1 --chain2-id=$CHAINID2 \ + --chain1-addr=tcp://localhost:${RPC_PORT1} --chain2-addr=tcp://localhost:${RPC_PORT2} \ + --from=$RELAY_KEY &> relay.log & ``` -Now that `test_chain_2` knows about some recent state of `test_chain_1`, we can post the packet to `test_chain_2`, -along with proof the packet was committed on `test_chain_1`. Since `test_chain_2` knows about some recent state -of `test_chain_1`, it will be able to verify the proof! +This should start up the relay, and assuming no error messages came out, +the two chains are now fully connected over IBC. Let's use this to send +our first tx accross the chains... -First, output the height, packet, and proof for reference: +### Sending cross-chain payments + +The hard part is over, we set up two blockchains, a few private keys, and +a secure relay between them. Now we can enjoy the fruits of our labor... ``` -echo $QUERY | jq .height -echo $QUERY | jq .value -echo $QUERY | jq .proof +# Here's an emptt account on test-chain-2 +basecli2 query account $BROKE ``` -And forward those values to `test_chain_2`: +``` +# Let's send some funds from test-chain-1 +basecli1 tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$BROKE --name=money +``` ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height --packet 0x --proof 0x +# give it time to arrive... +sleep 2 +# now you should see 12345 coins! +basecli2 query account $BROKE ``` -If the command does not return an error, then we have successfuly transfered data from `test_chain_1` to `test_chain_2`. Tada! +You're no longer broke! Cool, huh? +Now have fun exploring and sending coins across the chains. +And making more accounts as you want to. ## Conclusion -In this tutorial we explained how IBC works, and demonstrated how to use it to communicate between two chains. -We did the simplest communciation possible: a one way transfer of data from chain1 to chain2. -The most important part was that we updated chain2 with the latest state (i.e. header and commit) of chain1, -and then were able to post a proof to chain2 that a packet was committed to the outgoing state of chain1. +In this tutorial we explained how IBC works, and demonstrated how to use it to +communicate between two chains. We did the simplest communciation possible: a +one way transfer of data from chain1 to chain2. The most important part was +that we updated chain2 with the latest state (i.e. header and commit) of +chain1, and then were able to post a proof to chain2 that a packet was +committed to the outgoing state of chain1. -In a future tutorial, we will demonstrate how to use IBC to actually transfer tokens between two blockchains, -but we'll do it with real testnets deployed across multiple nodes on the network. Stay tuned! +In a future tutorial, we will demonstrate how to use IBC to actually transfer +tokens between two blockchains, but we'll do it with real testnets deployed +across multiple nodes on the network. Stay tuned! diff --git a/docs/guide/install.md b/docs/guide/install.md index 9c5738a7732..cbf3fc7b8af 100644 --- a/docs/guide/install.md +++ b/docs/guide/install.md @@ -3,7 +3,7 @@ On a good day, basecoin can be installed like a normal Go program: ``` -go get -u github.com/tendermint/basecoin/cmd/basecoin +go get -u github.com/tendermint/basecoin/cmd/... ``` In some cases, if that fails, or if another branch is required, @@ -14,8 +14,7 @@ the correct way to install is: ``` cd $GOPATH/src/github.com/tendermint/basecoin git pull origin master -make get_vendor_deps -make install +make all ``` This will create the `basecoin` binary in `$GOPATH/bin`. diff --git a/docs/guide/key-management.md b/docs/guide/key-management.md new file mode 100644 index 00000000000..50a99899d0c --- /dev/null +++ b/docs/guide/key-management.md @@ -0,0 +1,20 @@ +# Key Management + +Here we explain a bit how to real with your keys, using the `basecli keys` subcommand. + +**TODO** + +## Creating keys + +Create the keys and store a key phrase. No other way to recover it. + +``` +SEED=$(echo 1234567890 | basecli keys new fred -o json | jq .seed | tr -d \") +echo $SEED +(echo qwertyuiop; echo $SEED stamp) | basecli keys recover oops +(echo qwertyuiop; echo $SEED) | basecli keys recover derf +basecli keys get fred -o json +basecli keys get derf -o json +``` + +You can type it in to recover... try to do this by hand. diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go deleted file mode 100644 index b439176266d..00000000000 --- a/docs/guide/src/example-plugin/cmd.go +++ /dev/null @@ -1,62 +0,0 @@ -package main - -import ( - "github.com/spf13/cobra" - - wire "github.com/tendermint/go-wire" - - "github.com/tendermint/basecoin/cmd/commands" - "github.com/tendermint/basecoin/types" -) - -var ( - //CLI Flags - validFlag bool - - //CLI Plugin Commands - ExamplePluginTxCmd = &cobra.Command{ - Use: "example", - Short: "Create, sign, and broadcast a transaction to the example plugin", - RunE: examplePluginTxCmd, - } -) - -//Called during CLI initialization -func init() { - - //Set the Plugin Flags - ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") - - //Register a plugin specific CLI command as a subcommand of the tx command - commands.RegisterTxSubcommand(ExamplePluginTxCmd) - - //Register the example with basecoin at start - commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) -} - -//Send a transaction -func examplePluginTxCmd(cmd *cobra.Command, args []string) error { - - // Create a transaction using the flag. - // The tx passes on custom information to the plugin - exampleTx := ExamplePluginTx{validFlag} - - // The tx is passed to the plugin in the form of - // a byte array. This is achieved by serializing the object using go-wire. - // Once received in the plugin, these exampleTxBytes are decoded back - // into the original ExamplePluginTx struct - exampleTxBytes := wire.BinaryBytes(exampleTx) - - // Send the transaction and return any errors. - // Here exampleTxBytes is packaged in the `tx.Data` field of an AppTx, - // and passed on to the plugin through the following sequence: - // - passed as `data` to `commands.AppTx` (cmd/commands/tx.go) - // - set as the `tx.Data` field of an AppTx, which is then passed to commands.broadcastTx (cmd/commands/tx.go) - // - the tx is broadcast to Tendermint, which runs it through app.CheckTx (app/app.go) - // - after passing CheckTx, it will eventually be included in a block and run through app.DeliverTx (app/app.go) - // - DeliverTx receives txBytes, which is the serialization of the full AppTx (app/app.go) - // - Once deserialized, the tx is passed to `state.ExecTx` (state/execution.go) - // - If the tx passes various checks, the `tx.Data` is forwarded as `txBytes` to `plugin.RunTx` (docs/guide/src/example-plugin/plugin.go) - // - Finally, it deserialized back to the ExamplePluginTx - return commands.AppTx("example-plugin", exampleTxBytes) -} diff --git a/docs/guide/src/example-plugin/main.go b/docs/guide/src/example-plugin/main.go deleted file mode 100644 index 328e22025bd..00000000000 --- a/docs/guide/src/example-plugin/main.go +++ /dev/null @@ -1,35 +0,0 @@ -package main - -import ( - "os" - - "github.com/spf13/cobra" - - "github.com/tendermint/basecoin/cmd/commands" - "github.com/tendermint/tmlibs/cli" -) - -func main() { - - //Initialize example-plugin root command - var RootCmd = &cobra.Command{ - Use: "example-plugin", - Short: "example-plugin usage description", - } - - //Add the default basecoin commands to the root command - RootCmd.AddCommand( - commands.InitCmd, - commands.StartCmd, - commands.TxCmd, - commands.QueryCmd, - commands.KeyCmd, - commands.VerifyCmd, - commands.BlockCmd, - commands.AccountCmd, - commands.UnsafeResetAllCmd, - ) - - cmd := cli.PrepareMainCmd(RootCmd, "BC", os.ExpandEnv("$HOME/.basecoin-example-plugin")) - cmd.Execute() -} diff --git a/docs/guide/src/example-plugin/plugin.go b/docs/guide/src/example-plugin/plugin.go deleted file mode 100644 index 97e80520a4c..00000000000 --- a/docs/guide/src/example-plugin/plugin.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -import ( - abci "github.com/tendermint/abci/types" - "github.com/tendermint/basecoin/types" - "github.com/tendermint/go-wire" -) - -//----------------------------------------- -// Structs -// * Note the fields in each struct may be expanded/modified - -// Plugin State Struct -// * Intended to store the current state of the plugin -// * This example contains a field which holds the execution count -// * Serialized (by go-wire) and stored within the KVStore using the key retrieved -// from the ExamplePlugin.StateKey() function/ -// * All fields must be exposed for serialization by external libs (here go-wire) -type ExamplePluginState struct { - Counter int -} - -// Transaction Struct -// * Stores transaction-specific plugin-customized information -// * This example contains a dummy field 'Valid' intended to specify -// if the transaction is a valid and should proceed -// * Deserialized (by go-wire) from txBytes in ExamplePlugin.RunTx -// * All fields must be exposed for serialization by external libs (here go-wire) -type ExamplePluginTx struct { - Valid bool -} - -// Plugin Struct -// * Struct which satisfies the basecoin Plugin interface -// * Stores global plugin settings, in this example just the plugin name -type ExamplePlugin struct { - name string -} - -//----------------------------------------- -// Non-Mandatory Functions - -// Return a new ExamplePlugin pointer with a hard-coded name -func NewExamplePlugin() *ExamplePlugin { - return &ExamplePlugin{ - name: "example-plugin", - } -} - -// Return a byte array unique to this plugin which is used as the key -// to store the plugin state (ExamplePluginState) -func (ep *ExamplePlugin) StateKey() []byte { - return []byte("ExamplePlugin.State") -} - -//----------------------------------------- -// Basecoin Plugin Interface Functions - -//Return the name of the plugin -func (ep *ExamplePlugin) Name() string { - return ep.name -} - -// SetOption may be called during genesis of basecoin and can be used to set -// initial plugin parameters. Within genesis.json file entries are made in -// the format: "/", "" Where is the plugin name, -// in this file ExamplePlugin.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 as the plugin state. Within this example -// SetOption is left unimplemented. -func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) { - return "" -} - -// The core tx logic of the app is containted within the RunTx function -// Input fields: -// - store types.KVStore -// - This term provides read/write capabilities to the merkelized data store -// which holds the basecoin state and is accessible to all plugins -// - ctx types.CallContext -// - The ctx contains the callers address, a pointer to the callers account, -// and an amount of coins sent with the transaction -// - txBytes []byte -// - Used to send customized information to your plugin -// -// Other more complex plugins may have a variant on the process order within this -// example including loading and saving multiple or variable states, or not -// including a state stored in the KVStore whatsoever. -func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { - - // Decode txBytes using go-wire. Attempt to write the txBytes to the variable - // tx, if the txBytes have not been properly encoded from a ExamplePluginTx - // struct wire will produce an error. - var tx ExamplePluginTx - err := wire.ReadBinaryBytes(txBytes, &tx) - if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) - } - - // Perform Transaction Validation - if !tx.Valid { - return abci.ErrInternalError.AppendLog("Valid must be true") - } - - // Load PluginState - var pluginState ExamplePluginState - stateBytes := store.Get(ep.StateKey()) - // If the state does not exist, stateBytes will be initialized - // as an empty byte array with length of zero - if len(stateBytes) > 0 { - err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire - if err != nil { - return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) - } - } - - //App Logic - pluginState.Counter += 1 - - // Save PluginState - store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) - - return abci.OK -} - -func (ep *ExamplePlugin) InitChain(store types.KVStore, vals []*abci.Validator) { -} - -func (ep *ExamplePlugin) BeginBlock(store types.KVStore, hash []byte, header *abci.Header) { -} - -func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) abci.ResponseEndBlock { - return abci.ResponseEndBlock{} -} diff --git a/glide.lock b/glide.lock index 8379e9e6d91..461e15df802 100644 --- a/glide.lock +++ b/glide.lock @@ -1,12 +1,14 @@ -hash: b31c6e45072e1015b04b5a201fb5ffcbadb837f21fe4a499f3b7e93229ee1c45 -updated: 2017-06-02T09:22:48.505187474Z +hash: 6eb1119dccf2ab4d0adb870a14cb4408047119be53c8ec4afeaa281bd1d2b457 +updated: 2017-06-21T19:51:10.330315159+02:00 imports: - name: github.com/bgentry/speakeasy version: 4aabc24848ce5fd31929f7d1e4ea74d3709c14cd - name: github.com/btcsuite/btcd - version: 1ae306021e323ae11c71ffb8546fbd9019e6cb6f + version: b8df516b4b267acf2de46be593a9d948d1d2c420 subpackages: - btcec +- name: github.com/btcsuite/fastsha256 + version: 637e656429416087660c84436a2a035d69d54e2e - name: github.com/BurntSushi/toml version: b26d9c308763d68093482582cea63d69be07a0f0 - name: github.com/ebuchman/fail-test @@ -28,7 +30,7 @@ imports: - name: github.com/go-playground/universal-translator version: 71201497bace774495daed26a3874fd339e0b538 - name: github.com/go-stack/stack - version: 7a2f19628aabfe68f0766b59e74d6315f8347d22 + version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 - name: github.com/golang/protobuf version: b50ceb1fa9818fa4d78b016c2d4ae025593a7ce3 subpackages: @@ -39,13 +41,13 @@ imports: - name: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/handlers - version: 13d73096a474cac93275c679c7b8a2dc17ddba82 + version: a4043c62cc2329bacda331d33fc908ab11ef0ec3 - name: github.com/gorilla/mux - version: 392c28fe23e1c45ddba891b0320b3b5df220beea + version: bcd8bc72b08df0f70df986b97f95590779502d31 - name: github.com/gorilla/websocket version: a91eba7f97777409bc2c443f5534d41dd20c5720 - name: github.com/hashicorp/hcl - version: 392dba7d905ed5d04a5794ba89f558b27e2ba1ca + version: a4b07c25de5ff55ad3b8936cea69a79a3d95a855 subpackages: - hcl/ast - hcl/parser @@ -63,6 +65,8 @@ imports: version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/magiconair/properties version: 51463bfca2576e06c62a8504b5c0f06d61312647 +- name: github.com/mattn/go-isatty + version: 9622e0cc9d8f9be434ca605520ff9a16808fee47 - name: github.com/mitchellh/mapstructure version: cc8532a8e9a55ea36402aa21efdf403a60d34096 - name: github.com/pelletier/go-buffruneio @@ -78,11 +82,11 @@ imports: - name: github.com/spf13/cast version: acbeb36b902d72a7a4c18e8f3241075e7ab763e4 - name: github.com/spf13/cobra - version: 3454e0e28e69c1b8effa6b5123c8e4185e20d696 + version: db6b9a8b3f3f400c8ecb4a4d7d02245b8facad66 - name: github.com/spf13/jwalterweatherman - version: 8f07c835e5cc1450c082fe3a439cf87b0cbb2d99 + version: fa7ca7e836cf3a8bb4ebf799f472c12d7e903d66 - name: github.com/spf13/pflag - version: e57e3eeb33f795204c1ca35f56c44f83227c6e66 + version: 80fe0fb4eba54167e2ccae1c6c950e72abf61b73 - name: github.com/spf13/viper version: 0967fc9aceab2ce9da34061253ac10fb99bba5b2 - name: github.com/syndtr/goleveldb @@ -101,7 +105,7 @@ imports: - leveldb/table - leveldb/util - name: github.com/tendermint/abci - version: b86da575718079396af1f7fe3609ea34be6f855d + version: 7f5f48b6b9ec3964de4b07b6c3cd05d7c91aeee5 subpackages: - client - example/dummy @@ -113,7 +117,7 @@ imports: - edwards25519 - extra25519 - name: github.com/tendermint/go-crypto - version: 7dff40942a64cdeefefa9446b2d104750b349f8a + version: ad70b2222698a2018c4bf18bab86f3727621f492 subpackages: - cmd - keys @@ -121,13 +125,14 @@ imports: - keys/server - keys/server/types - keys/storage/filestorage + - keys/wordlist - name: github.com/tendermint/go-wire version: 5f88da3dbc1a72844e6dfaf274ce87f851d488eb subpackages: - data - data/base58 - name: github.com/tendermint/light-client - version: 424905d3813586ce7e64e18690676250a0595ad4 + version: b66b57d193d2fdeda7b999aebfdc9531cbee39b0 subpackages: - certifiers - certifiers/client @@ -139,17 +144,16 @@ imports: - commands/txs - proofs - name: github.com/tendermint/merkleeyes - version: 6b06ad654956c951b3d27e38bb566ae45aae1ff7 + version: ce7de05896f560ddcc9207f65fea9901835dd238 subpackages: - app - client - iavl - name: github.com/tendermint/tendermint - version: 2b5b0172531319ebc255a0ba638f6be666e5e46c + version: 4f0f50c62d41d39ad64e07ad642f705cc13c8229 subpackages: - blockchain - cmd/tendermint/commands - - cmd/tendermint/commands/flags - config - consensus - mempool @@ -172,7 +176,7 @@ imports: - types - version - name: github.com/tendermint/tmlibs - version: 6b619742ac69388dd591c30f55aaee46197b086e + version: bd9d0d1637dadf1330e167189d5e5031aadcda6f subpackages: - autofile - cli @@ -186,7 +190,7 @@ imports: - logger - merkle - name: golang.org/x/crypto - version: ab89591268e0c8b748cbe4047b00197516011af5 + version: c7af5bf2638a1164f2eb5467c39c6cffbd13a02e subpackages: - curve25519 - nacl/box @@ -207,7 +211,7 @@ imports: - lex/httplex - trace - name: golang.org/x/sys - version: 9c9d83fe39ed3fd2d9249fcf6b755891fff54b03 + version: 9ccfe848b9db8435a24c424abbc07a921adf1df5 subpackages: - unix - name: golang.org/x/text @@ -243,7 +247,7 @@ imports: version: cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b testImports: - name: github.com/davecgh/go-spew - version: 04cdfd42973bb9c8589fd6a731800cf222fde1a9 + version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9 subpackages: - spew - name: github.com/pmezard/go-difflib @@ -251,7 +255,7 @@ testImports: subpackages: - difflib - name: github.com/stretchr/testify - version: 4d4bfba8f1d1027c4fdbe371823030df51419987 + version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0 subpackages: - assert - require diff --git a/glide.yaml b/glide.yaml index 132e8db2442..7e7727aac48 100644 --- a/glide.yaml +++ b/glide.yaml @@ -7,22 +7,22 @@ import: - package: github.com/spf13/pflag - package: github.com/spf13/viper - package: github.com/tendermint/abci - version: ~0.5.0 + version: develop version: subpackages: - server - types - package: github.com/tendermint/go-crypto - version: ~0.2.0 + version: develop subpackages: - cmd - keys - package: github.com/tendermint/go-wire - version: ~0.6.2 + version: develop subpackages: - data - package: github.com/tendermint/light-client - version: ~0.10.0 + version: develop subpackages: - commands - commands/proofs @@ -30,12 +30,12 @@ import: - commands/txs - proofs - package: github.com/tendermint/merkleeyes - version: ~0.2.0 + version: develop subpackages: - client - iavl - package: github.com/tendermint/tendermint - version: ~0.10.0 + version: develop subpackages: - config - node @@ -46,7 +46,7 @@ import: - rpc/lib/types - types - package: github.com/tendermint/tmlibs - version: ~0.2.0 + version: develop subpackages: - cli - cli/flags diff --git a/plugins/ibc/ibc.go b/plugins/ibc/ibc.go index eb8a27f1b09..3a9b0f8c6e7 100644 --- a/plugins/ibc/ibc.go +++ b/plugins/ibc/ibc.go @@ -570,7 +570,17 @@ func verifyCommit(chainState BlockchainState, header *tm.Header, commit *tm.Comm chainID := chainState.ChainID vals := chainState.Validators valSet := tm.NewValidatorSet(vals) - blockID := commit.Precommits[0].BlockID // XXX: incorrect + + var blockID tm.BlockID + for _, pc := range commit.Precommits { + // XXX: incorrect. we want the one for +2/3, not just the first one + if pc != nil { + blockID = pc.BlockID + } + } + if blockID.IsZero() { + return errors.New("All precommits are nil!") + } // NOTE: Currently this only works with the exact same validator set. // Not this, but perhaps "ValidatorSet.VerifyCommitAny" should expose diff --git a/scripts/dist.sh b/scripts/dist.sh index 977b36ae2f3..f96d6454f98 100755 --- a/scripts/dist.sh +++ b/scripts/dist.sh @@ -21,6 +21,11 @@ DIR="$( cd -P "$( dirname "$SOURCE" )/.." && pwd )" # Change into that dir because we expect that. cd "$DIR" +# Delete the old dir +echo "==> Removing old directory..." +rm -rf build/pkg +mkdir -p build/pkg + # Do a hermetic build inside a Docker container. docker build -t tendermint/${REPO_NAME}-builder scripts/${REPO_NAME}-builder/ docker run --rm -e "BUILD_TAGS=$BUILD_TAGS" -v "$(pwd)":/go/src/github.com/tendermint/${REPO_NAME} tendermint/${REPO_NAME}-builder ./scripts/dist_build.sh diff --git a/scripts/dist_build.sh b/scripts/dist_build.sh index 2812bef8aef..4268a7633fe 100755 --- a/scripts/dist_build.sh +++ b/scripts/dist_build.sh @@ -18,11 +18,6 @@ GIT_IMPORT="github.com/tendermint/basecoin/version" XC_ARCH=${XC_ARCH:-"386 amd64 arm"} XC_OS=${XC_OS:-"solaris darwin freebsd linux windows"} -# Delete the old dir -echo "==> Removing old directory..." -rm -rf build/pkg -mkdir -p build/pkg - # Make sure build tools are available. make tools @@ -30,7 +25,7 @@ make tools make get_vendor_deps # Build! -echo "==> Building..." +echo "==> Building basecoin..." "$(which gox)" \ -os="${XC_OS}" \ -arch="${XC_ARCH}" \ @@ -40,6 +35,16 @@ echo "==> Building..." -tags="${BUILD_TAGS}" \ github.com/tendermint/basecoin/cmd/basecoin +echo "==> Building basecli..." +"$(which gox)" \ + -os="${XC_OS}" \ + -arch="${XC_ARCH}" \ + -osarch="!darwin/arm !solaris/amd64 !freebsd/amd64" \ + -ldflags "-X ${GIT_IMPORT}.GitCommit='${GIT_COMMIT}' -X ${GIT_IMPORT}.GitDescribe='${GIT_DESCRIBE}'" \ + -output "build/pkg/{{.OS}}_{{.Arch}}/basecli" \ + -tags="${BUILD_TAGS}" \ + github.com/tendermint/basecoin/cmd/basecli + # Zip all the files. echo "==> Packaging..." for PLATFORM in $(find ./build/pkg -mindepth 1 -maxdepth 1 -type d); do diff --git a/tests/cli/basictx.sh b/tests/cli/basictx.sh new file mode 100755 index 00000000000..58f27ceba7e --- /dev/null +++ b/tests/cli/basictx.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# These global variables are required for common.sh +SERVER_EXE=basecoin +CLIENT_EXE=basecli +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +POOR=${ACCOUNTS[4]} + +oneTimeSetUp() { + quickSetup .basecoin_test_basictx basictx-chain +} + +oneTimeTearDown() { + quickTearDown +} + +test00GetAccount() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + assertFalse "requires arg" "${CLIENT_EXE} query account" + + checkAccount $SENDER "0" "9007199254740992" + + ACCT2=$(${CLIENT_EXE} query account $RECV) + assertFalse "has no genesis account" $? +} + +test01SendTx() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1" + assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + checkAccount $SENDER "1" "9007199254740000" + checkAccount $RECV "0" "992" + + # Make sure tx is indexed + checkSendTx $HASH $TX_HEIGHT $SENDER "992" +} + +# Load common then run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/common.sh +. $DIR/shunit2 diff --git a/tests/cli/common.sh b/tests/cli/common.sh new file mode 100644 index 00000000000..576bef0a48a --- /dev/null +++ b/tests/cli/common.sh @@ -0,0 +1,180 @@ +# This is not executable, but helper functions for the other scripts + +# XXX XXX XXX XXX XXX +# The following global variables must be defined before calling common functions: +# SERVER_EXE=foobar # Server binary name +# CLIENT_EXE=foobarcli # Client binary name +# ACCOUNTS=(foo bar) # List of accounts for initialization +# RICH=${ACCOUNTS[0]} # Account to assign genesis balance + +# XXX Ex Usage: quickSetup $WORK_NAME $CHAIN_ID +# Desc: Start the program, use with shunit2 OneTimeSetUp() +quickSetup() { + # These are passed in as args + BASE_DIR=$HOME/$1 + CHAIN_ID=$2 + + rm -rf $BASE_DIR 2>/dev/null + mkdir -p $BASE_DIR + + # Set up client - make sure you use the proper prefix if you set + # a custom CLIENT_EXE + export BC_HOME=${BASE_DIR}/client + prepareClient + + # start basecoin server (with counter) + initServer $BASE_DIR $CHAIN_ID + if [ $? != 0 ]; then return 1; fi + + initClient $CHAIN_ID + if [ $? != 0 ]; then return 1; fi + + printf "...Testing may begin!\n\n\n" +} + +# XXX Ex Usage: quickTearDown +# Desc: close the test server, use with shunit2 OneTimeTearDown() +quickTearDown() { + printf "\n\nstopping $SERVER_EXE test server..." + kill -9 $PID_SERVER >/dev/null 2>&1 + sleep 1 +} + +############################################################ + +prepareClient() { + echo "Preparing client keys..." + ${CLIENT_EXE} reset_all + assertTrue $? + + for i in "${!ACCOUNTS[@]}"; do + newKey ${ACCOUNTS[$i]} + done +} + +# XXX Ex Usage1: initServer $ROOTDIR $CHAINID +# XXX Ex Usage2: initServer $ROOTDIR $CHAINID $PORTPREFIX +# Desc: Grabs the Rich account and gives it all genesis money +# port-prefix default is 4665{6,7,8} +initServer() { + echo "Setting up genesis..." + SERVE_DIR=$1/server + assertNotNull "no chain" $2 + CHAIN=$2 + SERVER_LOG=$1/${SERVER_EXE}.log + + GENKEY=$(${CLIENT_EXE} keys get ${RICH} | awk '{print $2}') + ${SERVER_EXE} init --chain-id $CHAIN $GENKEY --home=$SERVE_DIR >>$SERVER_LOG + + # optionally set the port + if [ -n "$3" ]; then + echo "setting port $3" + sed -ie "s/4665/$3/" $SERVE_DIR/config.toml + fi + + echo "Starting ${SERVER_EXE} server..." + startServer $SERVE_DIR $SERVER_LOG + return $? +} + +# XXX Ex Usage: startServer $SERVE_DIR $SERVER_LOG +startServer() { + ${SERVER_EXE} start --home=$1 >>$2 2>&1 & + sleep 5 + PID_SERVER=$! + disown + if ! ps $PID_SERVER >/dev/null; then + echo "**FAILED**" + cat $SERVER_LOG + return 1 + fi +} + +# XXX Ex Usage1: initClient $CHAINID +# XXX Ex Usage2: initClient $CHAINID $PORTPREFIX +# Desc: Initialize the client program +# port-prefix default is 46657 +initClient() { + echo "Attaching ${CLIENT_EXE} client..." + PORT=${2:-46657} + # hard-code the expected validator hash + ${CLIENT_EXE} init --chain-id=$1 --node=tcp://localhost:${PORT} --valhash=EB168E17E45BAEB194D4C79067FFECF345C64DE6 + assertTrue "initialized light-client" $? +} + +# XXX Ex Usage1: newKey $NAME +# XXX Ex Usage2: newKey $NAME $PASSWORD +# Desc: Generates key for given username and password +newKey(){ + assertNotNull "keyname required" "$1" + KEYPASS=${2:-qwertyuiop} + (echo $KEYPASS; echo $KEYPASS) | ${CLIENT_EXE} keys new $1 >/dev/null 2>/dev/null + assertTrue "created $1" $? + assertTrue "$1 doesn't exist" "${CLIENT_EXE} keys get $1" +} + +# XXX Ex Usage: getAddr $NAME +# Desc: Gets the address for a key name +getAddr() { + assertNotNull "keyname required" "$1" + RAW=$(${CLIENT_EXE} keys get $1) + assertTrue "no key for $1" $? + # print the addr + echo $RAW | cut -d' ' -f2 +} + +# Desc: Assumes just one coin, checks the balance of first coin in any case +checkAccount() { + # make sure sender goes down + ACCT=$(${CLIENT_EXE} query account $1) + if ! assertTrue "account must exist: $ACCT" $?; then + return 1 + fi + + if [ -n "$DEBUG" ]; then echo $ACCT; echo; fi + assertEquals "proper sequence" "$2" $(echo $ACCT | jq .data.sequence) + assertEquals "proper money" "$3" $(echo $ACCT | jq .data.coins[0].amount) + return $? +} + +# XXX Ex Usage: txSucceeded $? "$TX" +# Desc: Must be called right after the `tx` command, makes sure it got a success response +txSucceeded() { + if (assertTrue "sent tx: $2" $1); then + TX=$2 + assertEquals "good check: $TX" "0" $(echo $TX | jq .check_tx.code) + assertEquals "good deliver: $TX" "0" $(echo $TX | jq .deliver_tx.code) + else + return 1 + fi +} + +# XXX Ex Usage: checkSendTx $HASH $HEIGHT $SENDER $AMOUNT +# Desc: This looks up the tx by hash, and makes sure the height and type match +# and that the first input was from this sender for this amount +checkSendTx() { + TX=$(${CLIENT_EXE} query tx $1) + assertTrue "found tx" $? + if [ -n "$DEBUG" ]; then echo $TX; echo; fi + + assertEquals "proper height" $2 $(echo $TX | jq .height) + assertEquals "type=send" '"send"' $(echo $TX | jq .data.type) + assertEquals "proper sender" "\"$3\"" $(echo $TX | jq .data.data.inputs[0].address) + assertEquals "proper out amount" "$4" $(echo $TX | jq .data.data.outputs[0].coins[0].amount) + return $? +} + +# XXX Ex Usage: waitForBlock $port +# Desc: Waits until the block height on that node increases by one +waitForBlock() { + addr=http://localhost:$1 + 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.latest_block_height` + done +} + + diff --git a/tests/cli/counter.sh b/tests/cli/counter.sh new file mode 100755 index 00000000000..28ade7466c1 --- /dev/null +++ b/tests/cli/counter.sh @@ -0,0 +1,94 @@ +#!/bin/bash + +# These global variables are required for common.sh +SERVER_EXE=counter +CLIENT_EXE=countercli +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +POOR=${ACCOUNTS[4]} + +oneTimeSetUp() { + quickSetup .basecoin_test_counter counter-chain +} + +oneTimeTearDown() { + quickTearDown +} + +test00GetAccount() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + assertFalse "requires arg" "${CLIENT_EXE} query account" + + checkAccount $SENDER "0" "9007199254740992" + + ACCT2=$(${CLIENT_EXE} query account $RECV) + assertFalse "has no genesis account" $? +} + +test01SendTx() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + assertFalse "missing dest" "${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 2>/dev/null" + assertFalse "bad password" "echo foo | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null" + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH 2>/dev/null) + txSucceeded $? "$TX" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + checkAccount $SENDER "1" "9007199254740000" + checkAccount $RECV "0" "992" + + # make sure tx is indexed + checkSendTx $HASH $TX_HEIGHT $SENDER "992" +} + +test02GetCounter() { + COUNT=$(${CLIENT_EXE} query counter) + assertFalse "no default count" $? +} + +# checkCounter $COUNT $BALANCE +# Assumes just one coin, checks the balance of first coin in any case +checkCounter() { + # make sure sender goes down + ACCT=$(${CLIENT_EXE} query counter) + assertTrue "count is set" $? + assertEquals "proper count" "$1" $(echo $ACCT | jq .data.Counter) + assertEquals "proper money" "$2" $(echo $ACCT | jq .data.TotalFees[0].amount) +} + +test03AddCount() { + SENDER=$(getAddr $RICH) + assertFalse "bad password" "echo hi | ${CLIENT_EXE} tx counter --amount=1000mycoin --sequence=2 --name=${RICH} 2>/dev/null" + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx counter --amount=10mycoin --sequence=2 --name=${RICH} --valid --countfee=5mycoin) + txSucceeded $? "$TX" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + checkCounter "1" "5" + + # FIXME: cannot load apptx properly. + # Look at the stack trace + # This cannot be fixed with the current ugly apptx structure... + # Leave for refactoring + + # make sure tx is indexed + # echo hash $HASH + # TX=$(${CLIENT_EXE} query tx $HASH --trace) + # echo tx $TX + # if [-z assertTrue "found tx" $?]; then + # assertEquals "proper height" $TX_HEIGHT $(echo $TX | jq .height) + # assertEquals "type=app" '"app"' $(echo $TX | jq .data.type) + # assertEquals "proper sender" "\"$SENDER\"" $(echo $TX | jq .data.data.input.address) + # fi + # echo $TX +} + +# Load common then run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/common.sh +. $DIR/shunit2 diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh new file mode 100755 index 00000000000..9819f5d87db --- /dev/null +++ b/tests/cli/ibc.sh @@ -0,0 +1,178 @@ +#!/bin/bash + +#!/bin/bash + +# These global variables are required for common.sh +SERVER_EXE=basecoin +CLIENT_EXE=basecli +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +POOR=${ACCOUNTS[4]} + +# Uncomment the following line for full stack traces in error output +# CLIENT_EXE="basecli --trace" + +oneTimeSetUp() { + # These are passed in as args + BASE_DIR_1=$HOME/.basecoin_test_ibc/chain1 + CHAIN_ID_1=test-chain-1 + CLIENT_1=${BASE_DIR_1}/client + PREFIX_1=1234 + PORT_1=${PREFIX_1}7 + + BASE_DIR_2=$HOME/.basecoin_test_ibc/chain2 + CHAIN_ID_2=test-chain-2 + CLIENT_2=${BASE_DIR_2}/client + PREFIX_2=2345 + PORT_2=${PREFIX_2}7 + + # Clean up and create the test dirs + rm -rf $BASE_DIR_1 $BASE_DIR_2 2>/dev/null + mkdir -p $BASE_DIR_1 $BASE_DIR_2 + + # Set up client for chain 1- make sure you use the proper prefix if you set + # a custom CLIENT_EXE + BC_HOME=${CLIENT_1} prepareClient + BC_HOME=${CLIENT_2} prepareClient + + # Start basecoin server, giving money to the key in the first client + BC_HOME=${CLIENT_1} initServer $BASE_DIR_1 $CHAIN_ID_1 $PREFIX_1 + if [ $? != 0 ]; then return 1; fi + PID_SERVER_1=$PID_SERVER + + # Start second basecoin server, giving money to the key in the second client + BC_HOME=${CLIENT_2} initServer $BASE_DIR_2 $CHAIN_ID_2 $PREFIX_2 + if [ $? != 0 ]; then return 1; fi + PID_SERVER_2=$PID_SERVER + + # Connect both clients + BC_HOME=${CLIENT_1} initClient $CHAIN_ID_1 $PORT_1 + if [ $? != 0 ]; then return 1; fi + BC_HOME=${CLIENT_2} initClient $CHAIN_ID_2 $PORT_2 + if [ $? != 0 ]; then return 1; fi + + printf "...Testing may begin!\n\n\n" +} + +oneTimeTearDown() { + printf "\n\nstopping both $SERVER_EXE test servers... $PID_SERVER_1 $PID_SERVER_2" + kill -9 $PID_SERVER_1 + kill -9 $PID_SERVER_2 + sleep 1 +} + +test00GetAccount() { + SENDER_1=$(BC_HOME=${CLIENT_1} getAddr $RICH) + RECV_1=$(BC_HOME=${CLIENT_1} getAddr $POOR) + export BC_HOME=${CLIENT_1} + + assertFalse "requires arg" "${CLIENT_EXE} query account" + assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_1" + checkAccount $SENDER_1 "0" "9007199254740992" + + export BC_HOME=${CLIENT_2} + SENDER_2=$(getAddr $RICH) + RECV_2=$(getAddr $POOR) + + assertFalse "requires arg" "${CLIENT_EXE} query account" + assertFalse "has no genesis account" "${CLIENT_EXE} query account $RECV_2" + checkAccount $SENDER_2 "0" "9007199254740992" + + # Make sure that they have different addresses on both chains (they are random keys) + assertNotEquals "sender keys must be different" "$SENDER_1" "$SENDER_2" + assertNotEquals "recipient keys must be different" "$RECV_1" "$RECV_2" +} + +test01SendIBCTx() { + # Trigger a cross-chain sendTx... from RICH on chain1 to POOR on chain2 + # we make sure the money was reduced, but nothing arrived + SENDER=$(BC_HOME=${CLIENT_1} getAddr $RICH) + RECV=$(BC_HOME=${CLIENT_2} getAddr $POOR) + + export BC_HOME=${CLIENT_1} + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20002mycoin \ + --sequence=1 --to=${CHAIN_ID_2}/${RECV} --name=$RICH) + txSucceeded $? "$TX" + # an example to quit early if there is no point in more tests + if [ $? != 0 ]; then echo "aborting!"; return 1; fi + + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + # Make sure balance went down and tx is indexed + checkAccount $SENDER "1" "9007199254720990" + checkSendTx $HASH $TX_HEIGHT $SENDER "20002" + + # Make sure nothing arrived - yet + waitForBlock ${PORT_1} + assertFalse "no relay running" "BC_HOME=${CLIENT_2} ${CLIENT_EXE} query account $RECV" + + # Start the relay and wait a few blocks... + # (already sent a tx on chain1, so use higher sequence) + startRelay 2 1 + if [ $? != 0 ]; then echo "can't start relay"; cat ${BASE_DIR_1}/../relay.log; return 1; fi + + # Give it a little time, then make sure the money arrived + echo "waiting for relay..." + sleep 1 + waitForBlock ${PORT_1} + waitForBlock ${PORT_2} + + # Check the new account + echo "checking ibc recipient..." + BC_HOME=${CLIENT_2} checkAccount $RECV "0" "20002" + + # Stop relay + printf "stoping relay\n" + kill -9 $PID_RELAY +} + +# StartRelay $seq1 $seq2 +# startRelay hooks up a relay between chain1 and chain2 +# it needs the proper sequence number for $RICH on chain1 and chain2 as args +startRelay() { + # Send some cash to the default key, so it can send messages + RELAY_KEY=${BASE_DIR_1}/server/key.json + RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") + echo starting relay $PID_RELAY ... + + # Get paid on chain1 + export BC_HOME=${CLIENT_1} + SENDER=$(getAddr $RICH) + RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ + --sequence=$1 --to=$RELAY_ADDR --name=$RICH) + txSucceeded $? "$RES" + if [ $? != 0 ]; then echo "can't pay chain1!"; return 1; fi + + # Get paid on chain2 + export BC_HOME=${CLIENT_2} + SENDER=$(getAddr $RICH) + RES=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=100000mycoin \ + --sequence=$2 --to=$RELAY_ADDR --name=$RICH) + txSucceeded $? "$RES" + if [ $? != 0 ]; then echo "can't pay chain2!"; return 1; fi + + # Initialize the relay (register both chains) + ${SERVER_EXE} relay init --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ + --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ + --genesis1=${BASE_DIR_1}/server/genesis.json --genesis2=${BASE_DIR_2}/server/genesis.json \ + --from=$RELAY_KEY > ${BASE_DIR_1}/../relay.log + if [ $? != 0 ]; then echo "can't initialize relays"; cat ${BASE_DIR_1}/../relay.log; return 1; fi + + # Now start the relay (constantly send packets) + ${SERVER_EXE} relay start --chain1-id=$CHAIN_ID_1 --chain2-id=$CHAIN_ID_2 \ + --chain1-addr=tcp://localhost:${PORT_1} --chain2-addr=tcp://localhost:${PORT_2} \ + --from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log & + sleep 2 + PID_RELAY=$! + disown + + # Return an error if it dies in the first two seconds to make sure it is running + ps $PID_RELAY >/dev/null + return $? +} + +# Load common then run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory +. $DIR/common.sh +. $DIR/shunit2 diff --git a/tests/cli/restart.sh b/tests/cli/restart.sh new file mode 100755 index 00000000000..fb387a70141 --- /dev/null +++ b/tests/cli/restart.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# these are two globals to control all scripts (can use eg. counter instead) +SERVER_EXE=basecoin +CLIENT_EXE=basecli +ACCOUNTS=(jae ethan bucky rigel igor) +RICH=${ACCOUNTS[0]} +POOR=${ACCOUNTS[4]} + +oneTimeSetUp() { + quickSetup .basecoin_test_restart restart-chain +} + +oneTimeTearDown() { + quickTearDown +} + +test00PreRestart() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=992mycoin --sequence=1 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + checkAccount $SENDER "1" "9007199254740000" + checkAccount $RECV "0" "992" + + # make sure tx is indexed + checkSendTx $HASH $TX_HEIGHT $SENDER "992" + +} + +test01OnRestart() { + SENDER=$(getAddr $RICH) + RECV=$(getAddr $POOR) + + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=10000mycoin --sequence=2 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" + if [ $? != 0 ]; then echo "can't make tx!"; return 1; fi + + HASH=$(echo $TX | jq .hash | tr -d \") + TX_HEIGHT=$(echo $TX | jq .height) + + # wait til we have quite a few blocks... like at least 20, + # so the query command won't just wait for the next eg. 7 blocks to verify the result + echo "waiting to generate lots of blocks..." + sleep 5 + echo "done waiting!" + + # last minute tx just at the block cut-off... + TX=$(echo qwertyuiop | ${CLIENT_EXE} tx send --amount=20000mycoin --sequence=3 --to=$RECV --name=$RICH) + txSucceeded $? "$TX" + if [ $? != 0 ]; then echo "can't make second tx!"; return 1; fi + + # now we do a restart... + quickTearDown + startServer $BASE_DIR/server $BASE_DIR/${SERVER_EXE}.log + if [ $? != 0 ]; then echo "can't restart server!"; return 1; fi + + # make sure queries still work properly, with all 3 tx now executed + echo "Checking state after restart..." + checkAccount $SENDER "3" "9007199254710000" + checkAccount $RECV "0" "30992" + + # make sure tx is indexed + checkSendTx $HASH $TX_HEIGHT $SENDER "10000" + + # for double-check of logs + if [ -n "$DEBUG" ]; then + cat $BASE_DIR/${SERVER_EXE}.log; + fi +} + + +# load and run these tests with shunit2! +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" #get this files directory + +# load common helpers +. $DIR/common.sh + +. $DIR/shunit2 + diff --git a/version/version.go b/version/version.go index a45ea4e72d8..8e82ffaffea 100644 --- a/version/version.go +++ b/version/version.go @@ -1,7 +1,7 @@ package version const Maj = "0" -const Min = "5" -const Fix = "2" +const Min = "6" +const Fix = "0" -const Version = "0.5.2" +const Version = "0.6.0"