From 8fda43384745200b9cabe3c17ce486575a38e957 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Fri, 3 Feb 2017 21:17:11 -0500 Subject: [PATCH 1/9] major docs upgrade --- README.md | 68 ++--- docs/guide/basecoin-basics.md | 145 +++++++++++ docs/guide/basecoin-design.md | 0 docs/guide/deployment.md | 7 + docs/guide/example-counter.md | 1 + docs/guide/ibc.md | 288 ++++++++++++++++++++++ docs/guide/install.md | 13 + docs/guide/more-examples.md | 16 ++ Plugins.md => docs/guide/plugin-design.md | 43 ---- 9 files changed, 488 insertions(+), 93 deletions(-) create mode 100644 docs/guide/basecoin-basics.md create mode 100644 docs/guide/basecoin-design.md create mode 100644 docs/guide/deployment.md create mode 100644 docs/guide/example-counter.md create mode 100644 docs/guide/ibc.md create mode 100644 docs/guide/install.md create mode 100644 docs/guide/more-examples.md rename Plugins.md => docs/guide/plugin-design.md (51%) diff --git a/README.md b/README.md index a44a40b97623..6c5e917cd28d 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,16 @@ DISCLAIMER: Basecoin is not associated with Coinbase.com, an excellent Bitcoin/Ethereum service. -Basecoin is a sample [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. This project has two main purposes: +Basecoin is an [ABCI application](https://github.com/tendermint/abci) designed to be used with the [tendermint consensus engine](https://tendermint.com/) to form a Proof-of-Stake cryptocurrency. +It also provides a general purpose framework for extending the feature-set of the cryptocurrency +by implementing plugins. - 1. As an example for anyone wishing to build a custom application using tendermint. - 2. As a framework for anyone wishing to build a tendermint-based currency, extensible using the plugin system. +Basecoin serves as a reference implementation for how we build ABCI applications in Go, +and is the framework in which we implement the [Cosmos Hub](https://cosmos.network). +It's easy to use, and doesn't require any forking - just implement your plugin, import the basecoin libraries, +and away you go with a full-stack blockchain and command line tool for transacting. -If you wish to use basecoin as a framework to build your application, you most likely do not need to fork basecoin or modify it in any way. In fact, even the cli tool is designed to be easily extended by third party repos with almost no copying of code. You just need to add basecoin as a dependency in the `vendor` dir and take a look at [some examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) of how to customize it without modifying the code. - -## Contents - - 1. [Installation](#installation) - 1. [Using the plugin system](#using-the-plugin-system) - 1. [Using the cli](#using-the-cli) - 1. [Tutorials and other reading](#tutorials-and-other-reading) - 1. [Contributing](#contributing) +WARNING: Currently uses plain-text private keys for transactions and is otherwise not production ready. ## Installation @@ -30,49 +26,21 @@ make install This will create the `basecoin` binary in `$GOPATH/bin`. -## Using the Plugin System - -Basecoin is designed to serve as a common base layer for developers building cryptocurrency applications. -It handles public-key authentication of transactions, maintaining the balance of arbitrary types of currency (BTC, ATOM, ETH, MYCOIN, ...), -sending currency (one-to-one or n-to-m multisig), and providing merkle-proofs of the state. -These are common factors that many people wish to have in a crypto-currency system, -so instead of trying to start from scratch, developers can extend the functionality of Basecoin using the plugin system, just writing the custom business logic they need, and leaving the rest to the basecoin system. - -Interested in building a plugin? Then [read more details here](./Plugins.md) and then you can follow a [simple tutorial](https://github.com/tendermint/basecoin-examples/blob/master/pluginDev/tutorial.md) to get your first plugin working. - -### Best Practices - -We are still trying out sort out the best practices for basecoin plugins, and ABCi apps in general. Flexibility is very powerful once one has mastered a system, but when starting out, it is nice to have a set of guidelines to follow (and then expand beyond when no longer needed). I have attempted to gather some [good design practices](https://github.com/tendermint/basecoin-examples/tree/master/trader#code-design) I have discovered/invented while building progress. These are not hard rules, but should give you a good start. And please give feedback to improve and extend them. +## Command Line Interface -## Using the CLI - -The basecoin cli can be used to start a stand-alone basecoin instance (`basecoin start`), +The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. -Or follow through a [step-by-step introduction](https://github.com/tendermint/basecoin-examples/blob/master/tutorial.md) to testing basecoin locally. - -## Tutorials and Other Reading - -See our [introductory blog post](https://cosmos.network/blog/cosmos-creating-interoperable-blockchains-part-1), which explains the motivation behind Basecoin. - -There are a [number of examples](https://github.com/tendermint/basecoin-examples/blob/master/README.md) along with some tutorials and introductory texts, that should give you some pointers on how to wirte you own plugins and integrate them into your own custom app. - -We are working on extending these examples, as well as documenting (and automating) setting up a testnet, and providing an example GUI for viewing basecoin, which can all be used as a starting point for your application. They should be published during the course of February 2017, so stay tuned.... - -## Contributing +## Learn more -We will merge in interesting plugin implementations and improvements to Basecoin. +1. Getting started with the [Basecoin tool](/docs/guide/basecoin-basics.md) +1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.md) +1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) +1. Learn more about [plugin design](/docs/guide/plugin-design.md) +1. See some [more example applications](/docs/guide/more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. -If you don't have much experience forking in go, there are a few tricks you want to keep in mind to avoid headaches. Basically, all imports in go are absolute from GOPATH, so if you fork a repo with more than one directory, and you put it under github.com/MYNAME/repo, all the code will start calling github.com/ORIGINAL/repo, which is very confusing. My preferred solution to this is as follows: - * Create your own fork on github, using the fork button. - * Go to the original repo checked out locally (from `go get`) - * `git remote rename origin upstream` - * `git remote add origin git@github.com:YOUR-NAME/basecoin.git` - * `git push -u origin master` - * You can now push all changes to your fork and all code compiles, all other code referencing the original repo, now references your fork. - * If you want to pull in updates from the original repo: - * `git fetch upstream` - * `git rebase upstream/master` (or whatever branch you want) diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md new file mode 100644 index 000000000000..34aaae4046a0 --- /dev/null +++ b/docs/guide/basecoin-basics.md @@ -0,0 +1,145 @@ +# Basecoin Basics + +Here we explain how to get started with a simple Basecoin blockchain, and how to send transactions between accounts using the `basecoin` tool. + +## Install + +Make sure you have [basecoin installed](install.md). +You will also need to [install tendermint](https://tendermint.com/intro/getting-started/download). + +## Initialization + +Basecoin is an ABCI application that runs on Tendermint, so we first need to initialize Tendermint: + +``` +tendermint init +``` + +This will create the necessary files for a single Tendermint node in `~/.tendermint`. +If you had previously run tendermint, make sure you reset the chain +(note this will delete all chain data, so back it up if you need it): + +``` +tendermint unsafe_reset_all +``` + +Now we need some initialization files for basecoin. +We have included some defaults in the basecoin directory, under `data`. +For purposes of convenience, change to that directory: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin/data +``` + +The directory contains a genesis file and two private keys. + +You can generate your own private keys with `tendermint gen_validator`, +and construct the `genesis.json` as you like. + +## Start + +Now we can start basecoin: + +``` +basecoin start --in-proc +``` + +This will initialize the chain with the `genesis.json` file from the current directory. If you want to specify another location, you can run: + +``` +basecoin start --in-proc --dir PATH/TO/CUSTOM/DATA +``` + +Note that `--in-proc` stands for "in process", which means +basecoin will be started with the Tendermint node running in the same process. +To start Tendermint in a separate process instead, use: + +``` +basecoin start +``` + +and in another window: + +``` +tendermint node +``` + +In either case, you should see blocks start streaming in! + +## Send transactions + +Now we are ready to send some transactions. +If you take a look at the `genesis.json` file, you will see one account listed there. +This account corresponds to the private key in `priv_validator.json`. +We also included the private key for another account, in `priv_validator2.json`. + +Let's check the balance of these two accounts: + +``` +basecoin account 0xD397BC62B435F3CF50570FBAB4340FE52C60858F +basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +``` + +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 sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 10 +``` + +By default, the CLI looks for a `priv_validator.json` to sign the transaction with, +so this will only work if you are in the `$GOPATH/src/github.com/tendermint/basecoin/data`. +To specify a different key, we can use the `--from` flag. + +Now if we check the second account, it should have `10` coins! + +``` +basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +``` + +We can send some of these coins back like so: + +``` +basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 5 +``` + +Note how we use the `--from` flag to select a different account to send from. + +If we try to send too much, we'll get an error: + +``` +basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 100 +``` + +See `basecoin sendtx --help` for additional details. + +## Plugins + + +The `sendtx` command creates and broadcasts a transaction of type `SendTx`, +which is only useful for moving tokens around. +Fortunately, Basecoin supports another transaction type, the `AppTx`, +which can trigger code registered via a plugin system. + +For instance, we implemented a simple plugin called `counter`, +which just counts the number of transactions it processed. +To run it, kill the other processes, run `tendermint unsafe_reset_all`, and then + +``` +basecoin start --in-proc --counter-plugin +``` + +Now in another window, we can send transactions with: + +``` +TODO +``` + +## Next steps + +1. Learn more about [Basecoin's design](basecoin-design.md) +1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) +1. Learn more about [plugin design](plugin-design.md) +1. See some [more example applications](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/docs/guide/deployment.md b/docs/guide/deployment.md new file mode 100644 index 000000000000..af3f7feb123a --- /dev/null +++ b/docs/guide/deployment.md @@ -0,0 +1,7 @@ +## Deployment + +Up until this point, we have only been testing the code as a stand-alone abci app, which is nice for developing, but it is no blockchain. Just a blockchain-ready application. + +This section will demonstrate how to launch your basecoin-based application along with a tendermint testnet and initialize the genesis block for fun and profit. + +**TODO** Maybe we link to a blog post for this??? diff --git a/docs/guide/example-counter.md b/docs/guide/example-counter.md new file mode 100644 index 000000000000..d97b73f4ff3e --- /dev/null +++ b/docs/guide/example-counter.md @@ -0,0 +1 @@ +Rigel explains how to build your own basecoin-based app diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md new file mode 100644 index 000000000000..96022310f362 --- /dev/null +++ b/docs/guide/ibc.md @@ -0,0 +1,288 @@ +# 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. + +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/plugin-design.md) +and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works. + +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. + +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. + +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 +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 some state from height H, we need the signatures and root +hash from the header at height 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 +to the latest header available, so long as the validator set has not changed +much. If the validator set is changing, the client needs to track these +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`. +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. + +### IBCRegisterChainTx + +The `IBCRegisterChainTx` is used to register one chain on another. +It contains the chain ID and genesis configuration of the chain to register: + +``` +type IBCRegisterChainTx struct { + BlockchainGenesis +} + +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. + + +### 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: + +``` +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. + +### 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 (ie. an integer that increments with every message sent between this pair of chains), +a packet type (eg. coin, data, etc.), +and a payload. + +``` +type IBCPacketCreateTx struct { + Packet +} + +type Packet struct { + 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. + +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: + +``` +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. + +### 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: + +``` +- Genesis configuration +- Latest state +- Headers for recent heights +``` + +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. + +## 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. + +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`. + +## Try it out + +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). + +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: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin/demo +``` + +The relevant data is now in the `data` directory. + +We can start the two chains as follows: + +``` +TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & +basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +``` + +and + +``` +TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & +basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +``` + +Note how we refer to the relevant data directories. Also note how we have to set the various addresses for the second node so as not to conflict with the first. + +We can now check on the status of the two chains: + +``` +curl localhost:46657/status +curl localhost:36657/status +``` + +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. + +In this tutorial, we're going to send some data from `test_chain_1` to `test_chain_2`. +For the sake of convenience, let's first set some environment variables: + +``` +export CHAIN_ID1=test_chain_1 +export CHAIN_ID2=test_chain_2 + +export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" +export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +``` + +Let's start by registering `test_chain_1` on `test_chain_2`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +``` + +Now we can create the outgoing packet on `test_chain_1`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +``` + +Note our payload is just `DEADBEEF`. +Now that the packet is committed in the chain, let's get some proof by querying: + +``` +basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 +``` + +The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet), +and a proof (ie. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree. + +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: + +``` +basecoin block +``` + +where `` is the height returned in the previous 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`: + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x +``` + +where `
` and `` are the hex-encoded header and commit returned by the previous `block` command. + +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! + +``` +basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +``` + +Here, `` is one greater than the height retuned by the previous `query` command, and `` and `` are the +`value` and `proof` returned in that same query. + +Tada! + + +## Conclusion diff --git a/docs/guide/install.md b/docs/guide/install.md new file mode 100644 index 000000000000..4b77255b7dbe --- /dev/null +++ b/docs/guide/install.md @@ -0,0 +1,13 @@ +# Install + +We use glide for dependency management. The prefered way of compiling from source is the following: + +``` +go get -d github.com/tendermint/basecoin/cmd/basecoin +cd $GOPATH/src/github.com/tendermint/basecoin +make get_vendor_deps +make install +``` + +This will create the `basecoin` binary in `$GOPATH/bin`. + diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md new file mode 100644 index 000000000000..ba1d94b134b3 --- /dev/null +++ b/docs/guide/more-examples.md @@ -0,0 +1,16 @@ + +## Mintcoin + + +You just read about the amazing [plugin system](https://github.com/tendermint/basecoin/blob/develop/Plugins.md), and want to use it to print your own money. Me too! Let's get started with a simple plugin extension to basecoin, called [mintcoin](./mintcoin/README.md). This plugin lets you register one or more accounts as "central bankers", who can unilaterally issue more currency into the system. It also serves as a simple test-bed to see how one can not just build a plugin, but also take advantage of existing codebases to provide a simple cli to use it. + +## Financial Instruments + +Sure, printing money and sending it is nice, but sometimes I don't fully trust the guy at the other end. Maybe we could add an escrow service? Or how about options for currency trading, since we support multiple currencies? No problem, this is also just a plugin away. Checkout our [trader application](./trader). + +**Running code, still WIP** + +## IBC + +Now, let's hook up your personal crypto-currency with the wide world of other currencies, in a distributed, proof-of-stake based exchange. Hard, you say? Well half the work is already done for you with the [IBC, InterBlockchain Communication, plugin](./ibc.md). Now, we just need to get cosmos up and running and time to go and trade. + diff --git a/Plugins.md b/docs/guide/plugin-design.md similarity index 51% rename from Plugins.md rename to docs/guide/plugin-design.md index 7562aceacc00..6be526a8aea3 100644 --- a/Plugins.md +++ b/docs/guide/plugin-design.md @@ -48,46 +48,3 @@ where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent. Basecoin also defines another transaction type, the `AppTx`: -``` -type AppTx struct { - Gas int64 `json:"gas"` // Gas - Fee Coin `json:"fee"` // Fee - Name string `json:"type"` // Which plugin - Input TxInput `json:"input"` - Data []byte `json:"data"` -} -``` - -The `AppTx` enables arbitrary additional functionality through the use of plugins. -A plugin is simply a Go package that implements the `Plugin` interface: - -``` -type Plugin interface { - - // Name of this plugin, should be short. - Name() string - - // Run a transaction from ABCI DeliverTx - RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) - - // Other ABCI message handlers - SetOption(store KVStore, key string, value string) (log string) - InitChain(store KVStore, vals []*abci.Validator) - BeginBlock(store KVStore, height uint64) - EndBlock(store KVStore, height uint64) []*abci.Validator -} - -type CallContext struct { - CallerAddress []byte // Caller's Address (hash of PubKey) - CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted - Coins Coins // The coins that the caller wishes to spend, excluding fees -} -``` - -The workhorse of the plugin is `RunTx`, which is called when an `AppTx` is processed. -The `Name` field in the `AppTx` refers to the plugin name, and the `Data` field of the `AppTx` is -forward to the `RunTx` function. - -You can look at some example plugins in the [basecoin repo](https://github.com/tendermint/basecoin/tree/develop/plugins). - -If you want to see how you can write a plugin in your own repo, and make use of all the basecoin tooling, cli, etc. please take a look at the [mintcoin example](https://github.com/tendermint/basecoin-examples/tree/master/mintcoin) for inspiration, not just the plugin itself, but also the `cmd/mintcoin` directory to create the custom command. From cb253cbdf4ea30ccec62e7cfee0bb1ae2a2c0d11 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Sun, 5 Feb 2017 00:43:55 -0500 Subject: [PATCH 2/9] docs: design, examples --- README.md | 2 +- docs/guide/basecoin-design.md | 81 ++++++++++++++++++++++++++++++++ docs/guide/more-examples.md | 11 ++++- docs/guide/plugin-design.md | 88 ++++++++++++++++++++++------------- 4 files changed, 147 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 6c5e917cd28d..b6c468aa60a5 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ See `basecoin --help` and `basecoin [cmd] --help` for more details`. 1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) 1. Learn more about [plugin design](/docs/guide/plugin-design.md) 1. See some [more example applications](/docs/guide/more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md) 1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index e69de29bb2d1..8240106c1199 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -0,0 +1,81 @@ +# Basecoin Design + +Basecoin is designed to be a simple cryptocurrency application with limitted built-in functionality, +but with the capacity to be extended by arbitrary plugins. +Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built in virtual machine. + +## Accounts + +The Basecoin state consists entirely of a set of accounts. +Each account contains an ED25519 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. + +Accounts are serialized and stored in a Merkle tree using the account's address as the key, +where the address is the RIPEMD160 hash of the public key. +In particular, an account is stored in the Merkle tree under the key `base/a/
`, +where `
` is the 20-byte address of the account. +We use an implementation of a Merkle, balanced, binary search tree, also known as 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: + +``` +type SendTx struct { + Gas int64 `json:"gas"` + Fee Coin `json:"fee"` + Inputs []TxInput `json:"inputs"` + Outputs []TxOutput `json:"outputs"` +} + +type TxInput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput + Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx + PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 +} + +type TxOutput struct { + Address []byte `json:"address"` // Hash of the PubKey + Coins Coins `json:"coins"` // +} + +type Coins []Coin + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +``` + +There are a few things to note. First, 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. + +Second, notice 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 derrived 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. + +## Next steps + +1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) +1. Learn more about [plugin design](plugin-design.md) +1. See some [more example applications](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md index ba1d94b134b3..ad17c77a6626 100644 --- a/docs/guide/more-examples.md +++ b/docs/guide/more-examples.md @@ -1,8 +1,17 @@ +# Plugin Examples + +Now that we've seen how to use Basecoin, talked about the design, +and looked at how to implement a simple plugin, let's take a look at some more interesting examples. ## Mintcoin +Basecoin does not provide any functionality for adding new tokens to the system. +The state is endowed with tokens by a `genesis.json` file which is read once when the system is first started. +From there, tokens can be sent to other accounts, even new accounts, but it's impossible to add more tokens to the system. +For this, we need a plugin. -You just read about the amazing [plugin system](https://github.com/tendermint/basecoin/blob/develop/Plugins.md), and want to use it to print your own money. Me too! Let's get started with a simple plugin extension to basecoin, called [mintcoin](./mintcoin/README.md). This plugin lets you register one or more accounts as "central bankers", who can unilaterally issue more currency into the system. It also serves as a simple test-bed to see how one can not just build a plugin, but also take advantage of existing codebases to provide a simple cli to use it. +The `mintcoin` plugin lets you register one or more accounts as "central bankers", +who can unilaterally issue more currency into the system. ## Financial Instruments diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md index 6be526a8aea3..127c51960b0f 100644 --- a/docs/guide/plugin-design.md +++ b/docs/guide/plugin-design.md @@ -1,50 +1,72 @@ # Basecoin Plugins -Basecoin is an extensible cryptocurrency module. -Each Basecoin account contains a ED25519 public key, -a balance in many different coin denominations, -and a strictly increasing sequence number for replay protection (like in Ethereum). -Accounts are serialized and stored in a merkle tree using the account's address as the key, -where the address is the RIPEMD160 hash of the public key. +Basecoin implements a simple cryptocurrency, which is useful in and of itself, +but is far more useful if it can support additional functionality. +Here we describe how that functionality can be achieved through a plugin system. -Sending tokens around is done via the `SendTx`, which 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: + +## AppTx + +In addition to the `SendTx`, Basecoin also defines another transaction type, the `AppTx`: ``` -type SendTx struct { - Gas int64 `json:"gas"` // Gas - Fee Coin `json:"fee"` // Fee - Inputs []TxInput `json:"inputs"` - Outputs []TxOutput `json:"outputs"` +type AppTx struct { + 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 } +``` -type TxInput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // - Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput - Signature crypto.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx - PubKey crypto.PubKey `json:"pub_key"` // Is present iff Sequence == 0 -} +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 transasaction, +and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing. -type TxOutput struct { - Address []byte `json:"address"` // Hash of the PubKey - Coins Coins `json:"coins"` // -} +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 -type Coins []Coin +A plugin is simply a Go package that implements the `Plugin` interface: -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` +``` +type Plugin interface { + + // Name of this plugin, should be short. + Name() string + + // Run a transaction from ABCI DeliverTx + RunTx(store KVStore, ctx CallContext, txBytes []byte) (res abci.Result) + + // Other ABCI message handlers + SetOption(store KVStore, key string, value string) (log string) + InitChain(store KVStore, vals []*abci.Validator) + BeginBlock(store KVStore, height uint64) + EndBlock(store KVStore, height uint64) []*abci.Validator } +type CallContext struct { + CallerAddress []byte // Caller's Address (hash of PubKey) + CallerAccount *Account // Caller's Account, w/ fee & TxInputs deducted + Coins Coins // The coins that the caller wishes to spend, excluding fees +} ``` -Note it also 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. +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-derrived cryptocurrency can be greatly extended. +One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! -Basecoin also defines another transaction type, the `AppTx`: +## Next steps +1. Examples of [Basecoin plugins](more-examples.md) +1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) +1. [Deploy testnets](deployment.md) running your basecoin application. From f77d43040b56175533519c34eaba01822bf3bce6 Mon Sep 17 00:00:00 2001 From: hcopperm Date: Mon, 6 Feb 2017 11:57:35 -0800 Subject: [PATCH 3/9] Fix typos --- docs/guide/basecoin-design.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index 8240106c1199..344bb62bb978 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -1,8 +1,8 @@ # Basecoin Design -Basecoin is designed to be a simple cryptocurrency application with limitted built-in functionality, +Basecoin is designed to be a simple cryptocurrency application with limited built-in functionality, but with the capacity to be extended by arbitrary plugins. -Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built in virtual machine. +Its basic data structures are inspired by Ethereum, but it is much simpler, as there is no built-in virtual machine. ## Accounts From abac65bacc3fe86c19a587680b08a2a6922626ec Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 13:16:41 -0500 Subject: [PATCH 4/9] cli: refactor ibc and counter into own binaries --- cmd/adam/main.go | 29 +++++ cmd/basecoin/main.go | 10 +- cmd/{basecoin => }/commands/flags.go | 85 -------------- cmd/{basecoin => }/commands/ibc.go | 108 +++++++++++++++--- cmd/{basecoin => }/commands/query.go | 0 cmd/{basecoin => }/commands/start.go | 18 +-- cmd/{basecoin => }/commands/tx.go | 69 ++++++----- cmd/{basecoin => }/commands/utils.go | 0 .../commands/counter.go => counter/cmd.go} | 44 +++---- cmd/counter/main.go | 22 ++++ 10 files changed, 207 insertions(+), 178 deletions(-) create mode 100644 cmd/adam/main.go rename cmd/{basecoin => }/commands/flags.go (60%) rename cmd/{basecoin => }/commands/ibc.go (74%) rename cmd/{basecoin => }/commands/query.go (100%) rename cmd/{basecoin => }/commands/start.go (84%) rename cmd/{basecoin => }/commands/tx.go (86%) rename cmd/{basecoin => }/commands/utils.go (100%) rename cmd/{basecoin/commands/counter.go => counter/cmd.go} (54%) create mode 100644 cmd/counter/main.go diff --git a/cmd/adam/main.go b/cmd/adam/main.go new file mode 100644 index 000000000000..ef55d7acedf1 --- /dev/null +++ b/cmd/adam/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + + "github.com/urfave/cli" +) + +func init() { + commands.RegisterIBC() +} + +func main() { + app := cli.NewApp() + app.Name = "adam" + app.Usage = "adam [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.QueryCmd, + commands.VerifyCmd, // TODO: move to merkleeyes? + commands.BlockCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 536dc6187995..3c31ae38fdda 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -3,7 +3,7 @@ package main import ( "os" - "github.com/tendermint/basecoin/cmd/basecoin/commands" + "github.com/tendermint/basecoin/cmd/commands" "github.com/urfave/cli" ) @@ -14,12 +14,10 @@ func main() { app.Version = "0.1.0" app.Commands = []cli.Command{ commands.StartCmd, - commands.SendTxCmd, - commands.AppTxCmd, - commands.IbcCmd, + commands.TxCmd, commands.QueryCmd, - commands.VerifyCmd, - commands.BlockCmd, + commands.VerifyCmd, // TODO: move to merkleeyes? + commands.BlockCmd, // TODO: move to adam? commands.AccountCmd, } app.Run(os.Args) diff --git a/cmd/basecoin/commands/flags.go b/cmd/commands/flags.go similarity index 60% rename from cmd/basecoin/commands/flags.go rename to cmd/commands/flags.go index 0ca74962ce45..dc917546876d 100644 --- a/cmd/basecoin/commands/flags.go +++ b/cmd/commands/flags.go @@ -31,11 +31,6 @@ var ( Name: "in-proc", Usage: "Run Tendermint in-process with the App", } - - IbcPluginFlag = cli.BoolFlag{ - Name: "ibc-plugin", - Usage: "Enable the ibc plugin", - } ) // tx flags @@ -106,86 +101,6 @@ var ( Value: "test_chain_id", Usage: "ID of the chain for replay protection", } - - ValidFlag = cli.BoolFlag{ - Name: "valid", - Usage: "Set valid field in CounterTx", - } -) - -// ibc flags -var ( - IbcChainIDFlag = cli.StringFlag{ - Name: "chain_id", - Usage: "ChainID for the new blockchain", - Value: "", - } - - IbcGenesisFlag = cli.StringFlag{ - Name: "genesis", - Usage: "Genesis file for the new blockchain", - Value: "", - } - - IbcHeaderFlag = cli.StringFlag{ - Name: "header", - Usage: "Block header for an ibc update", - Value: "", - } - - IbcCommitFlag = cli.StringFlag{ - Name: "commit", - Usage: "Block commit for an ibc update", - Value: "", - } - - IbcFromFlag = cli.StringFlag{ - Name: "from", - Usage: "Source ChainID", - Value: "", - } - - IbcToFlag = cli.StringFlag{ - Name: "to", - Usage: "Destination ChainID", - Value: "", - } - - IbcTypeFlag = cli.StringFlag{ - Name: "type", - Usage: "IBC packet type (eg. coin)", - Value: "", - } - - IbcPayloadFlag = cli.StringFlag{ - Name: "payload", - Usage: "IBC packet payload", - Value: "", - } - - IbcPacketFlag = cli.StringFlag{ - Name: "packet", - Usage: "hex-encoded IBC packet", - Value: "", - } - - IbcProofFlag = cli.StringFlag{ - Name: "proof", - Usage: "hex-encoded proof of IBC packet from source chain", - Value: "", - } - - IbcSequenceFlag = cli.IntFlag{ - Name: "sequence", - Usage: "sequence number for IBC packet", - Value: 0, - } - - IbcHeightFlag = cli.IntFlag{ - Name: "height", - Usage: "Height the packet became egress in source chain", - Value: 0, - } ) // proof flags diff --git a/cmd/basecoin/commands/ibc.go b/cmd/commands/ibc.go similarity index 74% rename from cmd/basecoin/commands/ibc.go rename to cmd/commands/ibc.go index 39f5aef8d4de..1b513d404730 100644 --- a/cmd/basecoin/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -9,6 +9,7 @@ import ( "github.com/urfave/cli" "github.com/tendermint/basecoin/plugins/ibc" + "github.com/tendermint/basecoin/types" cmn "github.com/tendermint/go-common" "github.com/tendermint/go-merkle" @@ -16,25 +17,99 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +// Register the IBC plugin at start and for transactions +func RegisterIBC() { + RegisterTxSubcommand(IbcCmd) + RegisterStartPlugin("ibc", func() types.Plugin { + return ibc.New() + }) +} + +//--------------------------------------------------------------------- +// ibc flags + var ( - IbcCmd = cli.Command{ - Name: "ibc", - Usage: "Send a transaction to the interblockchain (ibc) plugin", - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, + IbcChainIDFlag = cli.StringFlag{ + Name: "chain_id", + Usage: "ChainID for the new blockchain", + Value: "", + } - FromFlag, + IbcGenesisFlag = cli.StringFlag{ + Name: "genesis", + Usage: "Genesis file for the new blockchain", + Value: "", + } - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, + IbcHeaderFlag = cli.StringFlag{ + Name: "header", + Usage: "Block header for an ibc update", + Value: "", + } - NameFlag, - DataFlag, - }, + IbcCommitFlag = cli.StringFlag{ + Name: "commit", + Usage: "Block commit for an ibc update", + Value: "", + } + + IbcFromFlag = cli.StringFlag{ + Name: "from", + Usage: "Source ChainID", + Value: "", + } + + IbcToFlag = cli.StringFlag{ + Name: "to", + Usage: "Destination ChainID", + Value: "", + } + + IbcTypeFlag = cli.StringFlag{ + Name: "type", + Usage: "IBC packet type (eg. coin)", + Value: "", + } + + IbcPayloadFlag = cli.StringFlag{ + Name: "payload", + Usage: "IBC packet payload", + Value: "", + } + + IbcPacketFlag = cli.StringFlag{ + Name: "packet", + Usage: "hex-encoded IBC packet", + Value: "", + } + + IbcProofFlag = cli.StringFlag{ + Name: "proof", + Usage: "hex-encoded proof of IBC packet from source chain", + Value: "", + } + + IbcSequenceFlag = cli.IntFlag{ + Name: "sequence", + Usage: "sequence number for IBC packet", + Value: 0, + } + + IbcHeightFlag = cli.IntFlag{ + Name: "height", + Usage: "Height the packet became egress in source chain", + Value: 0, + } +) + +//--------------------------------------------------------------------- +// ibc commands + +var ( + IbcCmd = cli.Command{ + Name: "ibc", + Usage: "Send a transaction to the interblockchain (ibc) plugin", + Flags: TxFlags, Subcommands: []cli.Command{ IbcRegisterTxCmd, IbcUpdateTxCmd, @@ -108,6 +183,9 @@ var ( } ) +//--------------------------------------------------------------------- +// ibc command implementations + func cmdIBCRegisterTx(c *cli.Context) error { chainID := c.String("chain_id") genesisFile := c.String("genesis") diff --git a/cmd/basecoin/commands/query.go b/cmd/commands/query.go similarity index 100% rename from cmd/basecoin/commands/query.go rename to cmd/commands/query.go diff --git a/cmd/basecoin/commands/start.go b/cmd/commands/start.go similarity index 84% rename from cmd/basecoin/commands/start.go rename to cmd/commands/start.go index 225619399f9b..9ce201a108fd 100644 --- a/cmd/basecoin/commands/start.go +++ b/cmd/commands/start.go @@ -19,7 +19,6 @@ import ( tmtypes "github.com/tendermint/tendermint/types" "github.com/tendermint/basecoin/app" - "github.com/tendermint/basecoin/plugins/ibc" "github.com/tendermint/basecoin/types" ) @@ -40,7 +39,6 @@ var StartCmd = cli.Command{ DirFlag, InProcTMFlag, ChainIDFlag, - IbcPluginFlag, }, } @@ -51,10 +49,9 @@ type plugin struct { var plugins = []plugin{} -// RegisterStartPlugin is used to add another -func RegisterStartPlugin(flag cli.BoolFlag, init func() types.Plugin) { - StartCmd.Flags = append(StartCmd.Flags, flag) - plugins = append(plugins, plugin{name: flag.GetName(), init: init}) +// RegisterStartPlugin is used to enable a plugin +func RegisterStartPlugin(name string, initFunc func() types.Plugin) { + plugins = append(plugins, plugin{name: name, init: initFunc}) } func cmdStart(c *cli.Context) error { @@ -73,15 +70,10 @@ func cmdStart(c *cli.Context) error { // Create Basecoin app basecoinApp := app.NewBasecoin(eyesCli) - if c.Bool("ibc-plugin") { - basecoinApp.RegisterPlugin(ibc.New()) - } - // loop through all registered plugins and enable if desired + // register all plugins for _, p := range plugins { - if c.Bool(p.name) { - basecoinApp.RegisterPlugin(p.init()) - } + basecoinApp.RegisterPlugin(p.init()) } // If genesis file exists, set key-value options diff --git a/cmd/basecoin/commands/tx.go b/cmd/commands/tx.go similarity index 86% rename from cmd/basecoin/commands/tx.go rename to cmd/commands/tx.go index e68c3754d194..cba6e7063936 100644 --- a/cmd/basecoin/commands/tx.go +++ b/cmd/commands/tx.go @@ -16,61 +16,56 @@ import ( tmtypes "github.com/tendermint/tendermint/types" ) +var TxFlags = []cli.Flag{ + NodeFlag, + ChainIDFlag, + + FromFlag, + + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, +} + var ( + TxCmd = cli.Command{ + Name: "tx", + Usage: "Create, sign, and broadcast a transaction", + ArgsUsage: "", + Subcommands: []cli.Command{ + SendTxCmd, + AppTxCmd, + }, + } + SendTxCmd = cli.Command{ - Name: "sendtx", - Usage: "Broadcast a basecoin SendTx", + Name: "send", + Usage: "Create, sign, and broadcast a SendTx transaction", ArgsUsage: "", Action: func(c *cli.Context) error { return cmdSendTx(c) }, - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, - - ToFlag, - }, + Flags: append(TxFlags, ToFlag), } AppTxCmd = cli.Command{ - Name: "apptx", - Usage: "Broadcast a basecoin AppTx", + Name: "app", + Usage: "Create, sign, and broadcast a raw AppTx transaction", ArgsUsage: "", Action: func(c *cli.Context) error { return cmdAppTx(c) }, - Flags: []cli.Flag{ - NodeFlag, - ChainIDFlag, - - FromFlag, - - AmountFlag, - CoinFlag, - GasFlag, - FeeFlag, - SeqFlag, - - NameFlag, - DataFlag, - }, + Flags: append(TxFlags, NameFlag, DataFlag), // Subcommands are dynamically registered with plugins as needed Subcommands: []cli.Command{}, } ) -// RegisterTxPlugin is used to add another subcommand and create a custom -// apptx encoding. Look at counter.go for an example -func RegisterTxPlugin(cmd cli.Command) { - AppTxCmd.Subcommands = append(AppTxCmd.Subcommands, cmd) +// Register a subcommand of TxCmd to craft transactions for plugins +func RegisterTxSubcommand(cmd cli.Command) { + TxCmd.Subcommands = append(TxCmd.Subcommands, cmd) } func cmdSendTx(c *cli.Context) error { diff --git a/cmd/basecoin/commands/utils.go b/cmd/commands/utils.go similarity index 100% rename from cmd/basecoin/commands/utils.go rename to cmd/commands/utils.go diff --git a/cmd/basecoin/commands/counter.go b/cmd/counter/cmd.go similarity index 54% rename from cmd/basecoin/commands/counter.go rename to cmd/counter/cmd.go index 83f98218cfd5..6b5e02ab557e 100644 --- a/cmd/basecoin/commands/counter.go +++ b/cmd/counter/cmd.go @@ -1,48 +1,48 @@ -package commands +package main import ( "fmt" - "github.com/tendermint/basecoin/plugins/counter" - "github.com/tendermint/basecoin/types" wire "github.com/tendermint/go-wire" "github.com/urfave/cli" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/basecoin/plugins/counter" + "github.com/tendermint/basecoin/types" ) +func init() { + commands.RegisterTxSubcommand(CounterTxCmd) + commands.RegisterStartPlugin("counter", func() types.Plugin { + return counter.New("counter") + }) +} + var ( + ValidFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set valid field in CounterTx", + } + CounterTxCmd = cli.Command{ Name: "counter", - Usage: "Craft a transaction to the counter plugin", + Usage: "Create, sign, and broadcast a transaction to the counter plugin", Action: func(c *cli.Context) error { return cmdCounterTx(c) }, - Flags: []cli.Flag{ - ValidFlag, - }, - } - - CounterPluginFlag = cli.BoolFlag{ - Name: "counter-plugin", - Usage: "Enable the counter plugin", + Flags: append(commands.TxFlags, ValidFlag), } ) -func init() { - RegisterTxPlugin(CounterTxCmd) - RegisterStartPlugin(CounterPluginFlag, - func() types.Plugin { return counter.New("counter") }) -} - func cmdCounterTx(c *cli.Context) error { valid := c.Bool("valid") - parent := c.Parent() counterTx := counter.CounterTx{ Valid: valid, Fee: types.Coins{ { - Denom: parent.String("coin"), - Amount: int64(parent.Int("fee")), + Denom: c.String("coin"), + Amount: int64(c.Int("fee")), }, }, } @@ -52,5 +52,5 @@ func cmdCounterTx(c *cli.Context) error { data := wire.BinaryBytes(counterTx) name := "counter" - return AppTx(parent, name, data) + return commands.AppTx(c, name, data) } diff --git a/cmd/counter/main.go b/cmd/counter/main.go new file mode 100644 index 000000000000..11967841c1ee --- /dev/null +++ b/cmd/counter/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "counter" + app.Usage = "counter [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} From d54763965ea70bde0c22bac7cb579f2a09fba819 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:10:17 -0500 Subject: [PATCH 5/9] cli: key command --- cmd/adam/main.go | 1 + cmd/basecoin/main.go | 1 + cmd/commands/flags.go | 2 +- cmd/commands/ibc.go | 4 +- cmd/commands/key.go | 73 +++++++++++++++++++++++++++++++++++ cmd/commands/start.go | 10 ++--- cmd/commands/tx.go | 19 +++++---- cmd/counter/cmd.go | 4 +- cmd/counter/main.go | 1 + data/genesis.json | 2 +- data/key.json | 11 ++++++ data/key2.json | 11 ++++++ data/priv_validator.json | 17 -------- data/priv_validator2.json | 16 -------- docs/guide/basecoin-basics.md | 52 +++++++++---------------- 15 files changed, 135 insertions(+), 89 deletions(-) create mode 100644 cmd/commands/key.go create mode 100644 data/key.json create mode 100644 data/key2.json delete mode 100644 data/priv_validator.json delete mode 100644 data/priv_validator2.json diff --git a/cmd/adam/main.go b/cmd/adam/main.go index ef55d7acedf1..9be7255e7cd4 100644 --- a/cmd/adam/main.go +++ b/cmd/adam/main.go @@ -20,6 +20,7 @@ func main() { app.Commands = []cli.Command{ commands.StartCmd, commands.TxCmd, + commands.KeyCmd, commands.QueryCmd, commands.VerifyCmd, // TODO: move to merkleeyes? commands.BlockCmd, diff --git a/cmd/basecoin/main.go b/cmd/basecoin/main.go index 3c31ae38fdda..77f9827fdd8a 100644 --- a/cmd/basecoin/main.go +++ b/cmd/basecoin/main.go @@ -16,6 +16,7 @@ func main() { commands.StartCmd, commands.TxCmd, commands.QueryCmd, + commands.KeyCmd, commands.VerifyCmd, // TODO: move to merkleeyes? commands.BlockCmd, // TODO: move to adam? commands.AccountCmd, diff --git a/cmd/commands/flags.go b/cmd/commands/flags.go index dc917546876d..4670ee9fcdf6 100644 --- a/cmd/commands/flags.go +++ b/cmd/commands/flags.go @@ -56,7 +56,7 @@ var ( FromFlag = cli.StringFlag{ Name: "from", - Value: "priv_validator.json", + Value: "key.json", Usage: "Path to a private key to sign the transaction", } diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 1b513d404730..24274331958c 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -20,9 +20,7 @@ import ( // Register the IBC plugin at start and for transactions func RegisterIBC() { RegisterTxSubcommand(IbcCmd) - RegisterStartPlugin("ibc", func() types.Plugin { - return ibc.New() - }) + RegisterStartPlugin("ibc", func() types.Plugin { return ibc.New() }) } //--------------------------------------------------------------------- diff --git a/cmd/commands/key.go b/cmd/commands/key.go new file mode 100644 index 000000000000..2ebd74afd4d2 --- /dev/null +++ b/cmd/commands/key.go @@ -0,0 +1,73 @@ +package commands + +import ( + "fmt" + "io/ioutil" + + "github.com/urfave/cli" + + cmn "github.com/tendermint/go-common" + "github.com/tendermint/go-crypto" + "github.com/tendermint/go-wire" +) + +var ( + KeyCmd = cli.Command{ + Name: "key", + Usage: "Manage keys", + ArgsUsage: "", + Subcommands: []cli.Command{NewKeyCmd}, + } + + NewKeyCmd = cli.Command{ + Name: "new", + Usage: "Create a new private key", + ArgsUsage: "", + Action: func(c *cli.Context) error { + return cmdNewKey(c) + }, + } +) + +func cmdNewKey(c *cli.Context) error { + key := genKey() + keyJSON := wire.JSONBytesPretty(key) + fmt.Println(string(keyJSON)) + return nil +} + +//--------------------------------------------- +// simple implementation of a key + +type Key struct { + Address []byte `json:"address"` + PubKey crypto.PubKey `json:"pub_key"` + PrivKey crypto.PrivKey `json:"priv_key"` +} + +// Implements Signer +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() + return &Key{ + Address: privKey.PubKey().Address(), + PubKey: privKey.PubKey(), + PrivKey: privKey, + } +} + +func LoadKey(filePath string) *Key { + keyJSONBytes, err := ioutil.ReadFile(filePath) + if err != nil { + cmn.Exit(err.Error()) + } + key := wire.ReadJSON(&Key{}, keyJSONBytes, &err).(*Key) + if err != nil { + cmn.Exit(cmn.Fmt("Error reading PrivValidator from %v: %v\n", filePath, err)) + } + return key +} diff --git a/cmd/commands/start.go b/cmd/commands/start.go index 9ce201a108fd..9996e551c9fc 100644 --- a/cmd/commands/start.go +++ b/cmd/commands/start.go @@ -43,15 +43,15 @@ var StartCmd = cli.Command{ } type plugin struct { - name string - init func() types.Plugin + name string + newPlugin func() types.Plugin } var plugins = []plugin{} // RegisterStartPlugin is used to enable a plugin -func RegisterStartPlugin(name string, initFunc func() types.Plugin) { - plugins = append(plugins, plugin{name: name, init: initFunc}) +func RegisterStartPlugin(name string, newPlugin func() types.Plugin) { + plugins = append(plugins, plugin{name: name, newPlugin: newPlugin}) } func cmdStart(c *cli.Context) error { @@ -73,7 +73,7 @@ func cmdStart(c *cli.Context) error { // register all plugins for _, p := range plugins { - basecoinApp.RegisterPlugin(p.init()) + basecoinApp.RegisterPlugin(p.newPlugin()) } // If genesis file exists, set key-value options diff --git a/cmd/commands/tx.go b/cmd/commands/tx.go index cba6e7063936..9b8ce21cd861 100644 --- a/cmd/commands/tx.go +++ b/cmd/commands/tx.go @@ -82,18 +82,17 @@ func cmdSendTx(c *cli.Context) error { return errors.New("To address is invalid hex: " + err.Error()) } - // load the priv validator - // XXX: this is overkill for now, we need a keys solution - privVal := tmtypes.LoadPrivValidator(fromFile) + // load the priv key + privKey := LoadKey(fromFile) // get the sequence number for the tx - sequence, err := getSeq(c, privVal.Address) + sequence, err := getSeq(c, privKey.Address) if err != nil { return err } // craft the tx - input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) output := newOutput(to, coin, amount) tx := &types.SendTx{ Gas: int64(gas), @@ -104,7 +103,7 @@ func cmdSendTx(c *cli.Context) error { // sign that puppy signBytes := tx.SignBytes(chainID) - tx.Inputs[0].Signature = privVal.Sign(signBytes) + tx.Inputs[0].Signature = privKey.Sign(signBytes) fmt.Println("Signed SendTx:") fmt.Println(string(wire.JSONBytes(tx))) @@ -134,14 +133,14 @@ func AppTx(c *cli.Context, name string, data []byte) error { gas, fee := c.Int("gas"), int64(c.Int("fee")) chainID := c.String("chain_id") - privVal := tmtypes.LoadPrivValidator(fromFile) + privKey := tmtypes.LoadPrivValidator(fromFile) - sequence, err := getSeq(c, privVal.Address) + sequence, err := getSeq(c, privKey.Address) if err != nil { return err } - input := types.NewTxInput(privVal.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) + input := types.NewTxInput(privKey.PubKey, types.Coins{types.Coin{coin, amount}}, sequence) tx := &types.AppTx{ Gas: int64(gas), Fee: types.Coin{coin, fee}, @@ -150,7 +149,7 @@ func AppTx(c *cli.Context, name string, data []byte) error { Data: data, } - tx.Input.Signature = privVal.Sign(tx.SignBytes(chainID)) + tx.Input.Signature = privKey.Sign(tx.SignBytes(chainID)) fmt.Println("Signed AppTx:") fmt.Println(string(wire.JSONBytes(tx))) diff --git a/cmd/counter/cmd.go b/cmd/counter/cmd.go index 6b5e02ab557e..c125feb4f60e 100644 --- a/cmd/counter/cmd.go +++ b/cmd/counter/cmd.go @@ -13,9 +13,7 @@ import ( func init() { commands.RegisterTxSubcommand(CounterTxCmd) - commands.RegisterStartPlugin("counter", func() types.Plugin { - return counter.New("counter") - }) + commands.RegisterStartPlugin("counter", func() types.Plugin { return counter.New() }) } var ( diff --git a/cmd/counter/main.go b/cmd/counter/main.go index 11967841c1ee..72395a9b0753 100644 --- a/cmd/counter/main.go +++ b/cmd/counter/main.go @@ -15,6 +15,7 @@ func main() { app.Commands = []cli.Command{ commands.StartCmd, commands.TxCmd, + commands.KeyCmd, commands.QueryCmd, commands.AccountCmd, } diff --git a/data/genesis.json b/data/genesis.json index 7aea6cb9bc1c..3a4c177526ab 100644 --- a/data/genesis.json +++ b/data/genesis.json @@ -1,7 +1,7 @@ [ "base/chainID", "test_chain_id", "base/account", { - "pub_key": [1, "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF"], + "pub_key": [1, "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279"], "coins": [ { "denom": "blank", diff --git a/data/key.json b/data/key.json new file mode 100644 index 000000000000..7a8c075604fa --- /dev/null +++ b/data/key.json @@ -0,0 +1,11 @@ +{ + "address": "1B1BE55F969F54064628A63B9559E7C21C925165", + "priv_key": [ + 1, + "C70D6934B4F55F1B7BC33B56B9CA8A2061384AFC19E91E44B40C4BBA182953D10000000000000000000000000000000000000000000000000000000000000000" + ], + "pub_key": [ + 1, + "619D3678599971ED29C7529DDD4DA537B97129893598A17C82E3AC9A8BA95279" + ] +} diff --git a/data/key2.json b/data/key2.json new file mode 100644 index 000000000000..f6f8d3693a24 --- /dev/null +++ b/data/key2.json @@ -0,0 +1,11 @@ +{ + "address": "1DA7C74F9C219229FD54CC9F7386D5A3839F0090", + "priv_key": [ + 1, + "34BAE9E65CE8245FAD035A0E3EED9401BDE8785FFB3199ACCF8F5B5DDF7486A80000000000000000000000000000000000000000000000000000000000000000" + ], + "pub_key": [ + 1, + "352195DA90CB0B90C24295B90AEBA25A5A71BC61BAB2FE2387241D439698B7B8" + ] +} diff --git a/data/priv_validator.json b/data/priv_validator.json deleted file mode 100644 index 15d791924071..000000000000 --- a/data/priv_validator.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - ], - "pub_key": [ - 1, - "B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" - ] -} - diff --git a/data/priv_validator2.json b/data/priv_validator2.json deleted file mode 100644 index 08256d1fd855..000000000000 --- a/data/priv_validator2.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "address": "4793A333846E5104C46DD9AB9A00E31821B2F301", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, - "priv_key": [ - 1, - "13A04A552ABAA2CCFA1F618CF9C97F1FD59FC3EE4968FE87DF3637C9B0F2FAAA93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" - ], - "pub_key": [ - 1, - "93766F08BE7135E78DBFFA76B61BC7C52B96256EB4394A224B4EF8BCC954DE2E" - ] -} diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index 34aaae4046a0..c60480ed4f7e 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -35,6 +35,9 @@ The directory contains a genesis file and two private keys. You can generate your own private keys with `tendermint gen_validator`, and construct the `genesis.json` as you like. +Note, however, that you must be careful with the `chain_id` field, +as every transaction must contain the correct `chain_id` +(default is `test_chain_id`). ## Start @@ -65,26 +68,28 @@ tendermint node ``` In either case, you should see blocks start streaming in! +Note, however, that currently basecoin currently requires the +`develop` branch of tendermint for this to work. ## Send transactions Now we are ready to send some transactions. If you take a look at the `genesis.json` file, you will see one account listed there. -This account corresponds to the private key in `priv_validator.json`. -We also included the private key for another account, in `priv_validator2.json`. +This account corresponds to the private key in `key.json`. +We also included the private key for another account, in `key2.json`. Let's check the balance of these two accounts: ``` -basecoin account 0xD397BC62B435F3CF50570FBAB4340FE52C60858F -basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +basecoin account 0x1B1BE55F969F54064628A63B9559E7C21C925165 +basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 ``` 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 sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 10 +basecoin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 10 ``` By default, the CLI looks for a `priv_validator.json` to sign the transaction with, @@ -94,13 +99,13 @@ To specify a different key, we can use the `--from` flag. Now if we check the second account, it should have `10` coins! ``` -basecoin account 0x4793A333846E5104C46DD9AB9A00E31821B2F301 +basecoin account 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 ``` We can send some of these coins back like so: ``` -basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 5 +basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 5 ``` Note how we use the `--from` flag to select a different account to send from. @@ -108,38 +113,19 @@ Note how we use the `--from` flag to select a different account to send from. If we try to send too much, we'll get an error: ``` -basecoin sendtx --to 0xD397BC62B435F3CF50570FBAB4340FE52C60858F --from priv_validator2.json --amount 100 +basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.json --amount 100 ``` -See `basecoin sendtx --help` for additional details. +See `basecoin tx send --help` for additional details. ## Plugins - -The `sendtx` command creates and broadcasts a transaction of type `SendTx`, +The `tx send` command creates and broadcasts a transaction of type `SendTx`, which is only useful for moving tokens around. Fortunately, Basecoin supports another transaction type, the `AppTx`, which can trigger code registered via a plugin system. -For instance, we implemented a simple plugin called `counter`, -which just counts the number of transactions it processed. -To run it, kill the other processes, run `tendermint unsafe_reset_all`, and then - -``` -basecoin start --in-proc --counter-plugin -``` - -Now in another window, we can send transactions with: - -``` -TODO -``` - -## Next steps - -1. Learn more about [Basecoin's design](basecoin-design.md) -1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) -1. Learn more about [plugin design](plugin-design.md) -1. See some [more example applications](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +In the [next tutorial](example-counter.md), +we demonstrate how to implement a plugin +and extend the CLI to support new transaction types! +But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md) From 7335c8287c3fcc6d907a2695d3bd49e2fba27868 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:12:18 -0500 Subject: [PATCH 6/9] docs: example-plugin --- README.md | 4 +- docs/guide/example-counter.md | 1 - docs/guide/example-plugin.md | 419 ++++++++++++++++++++++++ docs/guide/src/example-plugin/cmd.go | 36 ++ docs/guide/src/example-plugin/main.go | 23 ++ docs/guide/src/example-plugin/plugin.go | 80 +++++ plugins/counter/counter.go | 4 +- 7 files changed, 562 insertions(+), 5 deletions(-) delete mode 100644 docs/guide/example-counter.md create mode 100644 docs/guide/example-plugin.md create mode 100644 docs/guide/src/example-plugin/cmd.go create mode 100644 docs/guide/src/example-plugin/main.go create mode 100644 docs/guide/src/example-plugin/plugin.go diff --git a/README.md b/README.md index b6c468aa60a5..a43c45d8a7c3 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ This will create the `basecoin` binary in `$GOPATH/bin`. The basecoin CLI can be used to start a stand-alone basecoin instance (`basecoin start`), or to start basecoin with tendermint in the same process (`basecoin start --in-proc`). -It can also be used to send transactions, eg. `basecoin sendtx --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` +It can also be used to send transactions, eg. `basecoin tx send --to 0x4793A333846E5104C46DD9AB9A00E31821B2F301 --amount 100` See `basecoin --help` and `basecoin [cmd] --help` for more details`. ## Learn more 1. Getting started with the [Basecoin tool](/docs/guide/basecoin-basics.md) 1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.md) -1. Make your own [cryptocurrency using Basecoin plugins](/docs/guide/example-counter.md) +1. Extend Basecoin [using the plugin system](/docs/guide/example-plugin.md) 1. Learn more about [plugin design](/docs/guide/plugin-design.md) 1. See some [more example applications](/docs/guide/more-examples.md) 1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md) diff --git a/docs/guide/example-counter.md b/docs/guide/example-counter.md deleted file mode 100644 index d97b73f4ff3e..000000000000 --- a/docs/guide/example-counter.md +++ /dev/null @@ -1 +0,0 @@ -Rigel explains how to build your own basecoin-based app diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md new file mode 100644 index 000000000000..1db3feed6e22 --- /dev/null +++ b/docs/guide/example-plugin.md @@ -0,0 +1,419 @@ +# Basecoin Example Plugin + +In the [previous tutorial](basecoin-basics.md), +we saw how to start a Basecoin blockchain and use the CLI to send transactions. +Here, we will demonstrate how to extend the blockchain and CLI to support a simple plugin. + +## Overview + +Creating a new plugin and CLI to support it requires a little bit of boilerplate, but not much. +For convenience, we've implemented an extremely simple example plugin that can be easily modified. +The example is under `docs/guide/src/example-plugin`. +To build your own plugin, copy this folder to a new location and start modifying it there. + +Let's take a look at the files in `docs/guide/src/example-plugin`: + +``` +cmd.go +main.go +plugin.go +``` + +The `main.go` is very simple and does not need to be changed: + +``` +func main() { + app := cli.NewApp() + app.Name = "example-plugin" + app.Usage = "example-plugin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.KeyCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} +``` + +It creates the CLI, exactly like the `basecoin` one. +However, if we want our plugin to be active, +we need to make sure it is registered with the application. +In addition, if we want to send transactions to our plugin, +we need to add a new command to the CLI. +This is where the `cmd.go` comes in. + +## Commands + +First, we register the plugin: + + +``` +func init() { + commands.RegisterTxSubcommand(ExamplePluginTxCmd) + commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) +} +``` + +This creates a new subcommand under `tx` (defined below), +and ensures the plugin is activated when we start the app. +Now we actually define the new command: + +``` +var ( + ExampleFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set this to make the transaction valid", + } + + ExamplePluginTxCmd = cli.Command{ + Name: "example", + Usage: "Create, sign, and broadcast a transaction to the example plugin", + Action: func(c *cli.Context) error { + return cmdExamplePluginTx(c) + }, + Flags: append(commands.TxFlags, ExampleFlag), + } +) + +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} +``` + +It's a simple command with one flag, which is just a boolean. +However, it actually inherits more flags from the Basecoin framework: + +``` +Flags: append(commands.TxFlags, ExampleFlag), +``` + +The `commands.TxFlags` is defined in `cmd/commands/tx.go`: + +``` +var TxFlags = []cli.Flag{ + NodeFlag, + ChainIDFlag, + + FromFlag, + + AmountFlag, + CoinFlag, + GasFlag, + FeeFlag, + SeqFlag, +} +``` + +It adds all the default flags for a Basecoin transaction. + +If we now compile and run our program, we can see all the options: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin +go install ./docs/guide/src/example-plugin +example-plugin tx example --help +``` + +The output: + +``` +NAME: + example-plugin tx example - Create, sign, and broadcast a transaction to the example plugin + +USAGE: + example-plugin tx example [command options] [arguments...] + +OPTIONS: + --node value Tendermint RPC address (default: "tcp://localhost:46657") + --chain_id value ID of the chain for replay protection (default: "test_chain_id") + --from value Path to a private key to sign the transaction (default: "key.json") + --amount value Amount of coins to send in the transaction (default: 0) + --coin value Specify a coin denomination (default: "blank") + --gas value The amount of gas for the transaction (default: 0) + --fee value The transaction fee (default: 0) + --sequence value Sequence number for the account (default: 0) + --valid Set this to make the transaction valid +``` + +Cool, eh? + +Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`: + +``` +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} +``` + +We read the flag from the CLI library, and then create the example transaction. +Remember that Basecoin itself only knows about two transaction types, `SendTx` and `AppTx`. +All plugin data must be serialized (ie. encoded as a byte-array) +and sent as data in an `AppTx`. The `commands.AppTx` function does this for us - +it creates an `AppTx` with the corresponding data, signs it, and sends it on to the blockchain. + +## RunTx + +Ok, now we're ready to actually look at the implementation of the plugin in `plugin.go`. +Note I'll leave out some of the methods as they don't serve any purpose for this example, +but are necessary boilerplate. +Your plugin may have additional requirements that utilize these other plugins. +Here's what's relevant for us: + +``` +type ExamplePluginState struct { + Counter int +} + +type ExamplePluginTx struct { + Valid bool +} + +type ExamplePlugin struct { + name string +} + +func (ep *ExamplePlugin) Name() string { + return ep.name +} + +func (ep *ExamplePlugin) StateKey() []byte { + return []byte("ExamplePlugin.State") +} + +func NewExamplePlugin() *ExamplePlugin { + return &ExamplePlugin{ + name: "example-plugin", + } +} + +func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) { + return "" +} + +func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + + // Decode tx + var tx ExamplePluginTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // Validate tx + if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") + } + + // Load PluginState + var pluginState ExamplePluginState + stateBytes := store.Get(ep.StateKey()) + if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + 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 +} +``` + +All we're doing here is defining a state and transaction type for our plugin, +and then using the `RunTx` method to define how the transaction updates the state. +Let's break down `RunTx` in parts. First, we deserialize the transaction: + + +``` +// Decode tx +var tx ExamplePluginTx +err := wire.ReadBinaryBytes(txBytes, &tx) +if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) +} +``` + +The transaction is expected to be serialized according to Tendermint's "wire" format, +as defined in the `github.com/tendermint/go-wire` package. +If it's not encoded properly, we return an error. + + +If the transaction deserializes currectly, we can now check if it's valid: + +``` +// Validate tx +if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") +} +``` + +The transaction is valid if the `Valid` field is set, otherwise it's not - simple as that. +Finally, we can update the state. In this example, the state simply counts how many valid transactions +we've processed. But the state itself is serialized and kept in some `store`, which is typically a Merkle tree. +So first we have to load the state from the store and deserialize it: + +``` +// Load PluginState +var pluginState ExamplePluginState +stateBytes := store.Get(ep.StateKey()) +if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + if err != nil { + return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) + } +} +``` + +Note the state is stored under `ep.StateKey()`, which is defined above as `ExamplePlugin.State`. +Finally, we can update the state's `Counter`, and save the state back to the store: + +``` +//App Logic +pluginState.Counter += 1 + +// Save PluginState +store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) + +return abci.OK +``` + +And that's it! Now that we have a simple plugin, let's see how to run it. + +## Running your plugin + +In the [previous tutorial](basecoin-basics.md), +we used a pre-generated `genesis.json` and `priv_validator.json` for the application. +This time, let's make our own. + +First, let's create a new directory and change into it: + +``` +mkdir example-data +cd example-data +``` + +Now, let's create a new private key: + +``` +example-plugin key new > key.json +``` + +Here's what my `key.json looks like: + +``` +{ + "address": "15F591CA434CFCCBDEC1D206F3ED3EBA207BFE7D", + "priv_key": [ + 1, + "737C629667A9EAADBB8E7CF792D5A8F63AA4BB51E06457DDD7FDCC6D7412AAAD43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3" + ], + "pub_key": [ + 1, + "43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3" + ] +} +``` + +Now we can make a `genesis.json` file and add an account with out public key: + +``` +[ + "base/chainID", "example-chain", + "base/account", { + "pub_key": [1, "43AA6C88034F9EB8D2717CA4BBFCBA745EFF19B13EFCD6F339EDBAAAFCD2F7B3"], + "coins": [ + { + "denom": "gold", + "amount": 1000000000, + } + ] + } +] +``` + +Here we've granted ourselves `1000000000` units of the `gold` token. + +Before we can start the blockchain, we must initialize and/or reset the tendermint state for a new blockchain: + +``` +tendermint init +tendermint unsafe_reset_all +``` + +Great, now we're ready to go. +To start the blockchain, simply run + +``` +example-plugin start --in-proc +``` + +In another window, we can try sending some transactions: + +``` +example-plugin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --amount 100 --coin gold --chain_id example-chain +``` + +Note the `--coin` and `--chain_id` flags. In the [previous tutorial](basecoin-basics.md), +we didn't need them because we were using the default coin type ("blank") and chain ID ("test_chain_id"). +Now that we're using custom values, we need to specify them explicitly on the command line. + +Ok, so that's how we can send a `SendTx` transaction using our `example-plugin` CLI, +but we were already able to do that with the `basecoin` CLI. +With our new CLI, however, we can also send an `ExamplePluginTx`: + +``` +example-plugin tx example --amount 1 --coin gold --chain_id example-chain +``` + +The transaction is invalid! That's because we didn't specify the `--valid` flag: + +``` +example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain +``` + +Tada! We successfuly created, signed, broadcast, and processed our custom transaction type. + +## Query + +Now that we've sent a transaction to update the state, let's query for the state. +Recall that the state is stored under the key `ExamplePlugin.State`: + + +``` +example-plugin query ExamplePlugin.State +``` + +Note the `"value":"0101"` piece. This is the serialized form of the state, +which contains only an integer. +If we send another transaction, and then query again, we'll see the value increment: + +``` +example-plugin tx example --valid --amount 1 --coin gold --chain_id example-chain +example-plugin query ExamplePlugin.State +``` + +Neat, right? 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 [tutorial on Interblockchain Communication](ibc.md), +we'll put this proof to work! + +## Conclusion + +In this tutorial we demonstrated how to create a new plugin and how to extend the +basecoin CLI to activate the plugin on the blockchain and to send transactions to it. +Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. +In the [next tutorial](more-examples.md), we tour through some other plugin examples, +adding features for minting new coins, voting, and changin the Tendermint validator set. +But first, you may want to learn a bit more about [the design of plugins](plugin-design.md) diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go new file mode 100644 index 000000000000..b2fb3ecdae80 --- /dev/null +++ b/docs/guide/src/example-plugin/cmd.go @@ -0,0 +1,36 @@ +package main + +import ( + wire "github.com/tendermint/go-wire" + "github.com/urfave/cli" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/tendermint/basecoin/types" +) + +func init() { + commands.RegisterTxSubcommand(ExamplePluginTxCmd) + commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) +} + +var ( + ExampleFlag = cli.BoolFlag{ + Name: "valid", + Usage: "Set this to make the transaction valid", + } + + ExamplePluginTxCmd = cli.Command{ + Name: "example", + Usage: "Create, sign, and broadcast a transaction to the example plugin", + Action: func(c *cli.Context) error { + return cmdExamplePluginTx(c) + }, + Flags: append(commands.TxFlags, ExampleFlag), + } +) + +func cmdExamplePluginTx(c *cli.Context) error { + exampleFlag := c.Bool("valid") + exampleTx := ExamplePluginTx{exampleFlag} + return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx)) +} diff --git a/docs/guide/src/example-plugin/main.go b/docs/guide/src/example-plugin/main.go new file mode 100644 index 000000000000..e1347334c1dd --- /dev/null +++ b/docs/guide/src/example-plugin/main.go @@ -0,0 +1,23 @@ +package main + +import ( + "os" + + "github.com/tendermint/basecoin/cmd/commands" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = "example-plugin" + app.Usage = "example-plugin [command] [args...]" + app.Version = "0.1.0" + app.Commands = []cli.Command{ + commands.StartCmd, + commands.TxCmd, + commands.KeyCmd, + commands.QueryCmd, + commands.AccountCmd, + } + app.Run(os.Args) +} diff --git a/docs/guide/src/example-plugin/plugin.go b/docs/guide/src/example-plugin/plugin.go new file mode 100644 index 000000000000..81bb365e50dc --- /dev/null +++ b/docs/guide/src/example-plugin/plugin.go @@ -0,0 +1,80 @@ +package main + +import ( + abci "github.com/tendermint/abci/types" + "github.com/tendermint/basecoin/types" + "github.com/tendermint/go-wire" +) + +type ExamplePluginState struct { + Counter int +} + +type ExamplePluginTx struct { + Valid bool +} + +type ExamplePlugin struct { + name string +} + +func (ep *ExamplePlugin) Name() string { + return ep.name +} + +func (ep *ExamplePlugin) StateKey() []byte { + return []byte("ExamplePlugin.State") +} + +func NewExamplePlugin() *ExamplePlugin { + return &ExamplePlugin{ + name: "example-plugin", + } +} + +func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) { + return "" +} + +func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) { + + // Decode tx + var tx ExamplePluginTx + err := wire.ReadBinaryBytes(txBytes, &tx) + if err != nil { + return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) + } + + // Validate tx + if !tx.Valid { + return abci.ErrInternalError.AppendLog("Valid must be true") + } + + // Load PluginState + var pluginState ExamplePluginState + stateBytes := store.Get(ep.StateKey()) + if len(stateBytes) > 0 { + err = wire.ReadBinaryBytes(stateBytes, &pluginState) + 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, height uint64) { +} + +func (ep *ExamplePlugin) EndBlock(store types.KVStore, height uint64) []*abci.Validator { + return nil +} diff --git a/plugins/counter/counter.go b/plugins/counter/counter.go index 9c115089b5b3..e5d86a56cf21 100644 --- a/plugins/counter/counter.go +++ b/plugins/counter/counter.go @@ -32,9 +32,9 @@ func (cp *CounterPlugin) StateKey() []byte { return []byte(fmt.Sprintf("CounterPlugin{name=%v}.State", cp.name)) } -func New(name string) *CounterPlugin { +func New() *CounterPlugin { return &CounterPlugin{ - name: name, + name: "counter", } } From b7ef4652ed98179f9ee0377571bf723584bfbf58 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 16:28:41 -0500 Subject: [PATCH 7/9] docs: update links and flow --- docs/guide/basecoin-basics.md | 2 +- docs/guide/basecoin-design.md | 40 ++++++++++++++++++++--------------- docs/guide/example-plugin.md | 5 +++-- docs/guide/plugin-design.md | 9 ++++---- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index c60480ed4f7e..fc6e62bb29aa 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -125,7 +125,7 @@ which is only useful for moving tokens around. Fortunately, Basecoin supports another transaction type, the `AppTx`, which can trigger code registered via a plugin system. -In the [next tutorial](example-counter.md), +In the [next tutorial](example-plugin.md), we demonstrate how to implement a plugin and extend the CLI to support new transaction types! But first, you may want to learn a bit more about [Basecoin's design](basecoin-design.md) diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md index 344bb62bb978..fd675e0a5756 100644 --- a/docs/guide/basecoin-design.md +++ b/docs/guide/basecoin-design.md @@ -14,11 +14,26 @@ 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. +``` +type Account struct { + PubKey crypto.PubKey `json:"pub_key"` // May be nil, if not known. + Sequence int `json:"sequence"` + Balance Coins `json:"coins"` +} + +type Coins []Coin + +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} +``` + Accounts are serialized and stored in a Merkle tree using the account's address as the key, -where the address is the RIPEMD160 hash of the public key. In particular, an account is stored in the Merkle tree under the key `base/a/
`, -where `
` is the 20-byte address of the account. -We use an implementation of a Merkle, balanced, binary search tree, also known as an [IAVL tree](https://github.com/tendermint/go-merkle). +where `
` is the address of the account. +In Basecoin, the address of an account is the 20-byte `RIPEMD160` hash of the public key. +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 @@ -47,14 +62,6 @@ type TxOutput struct { Address []byte `json:"address"` // Hash of the PubKey Coins Coins `json:"coins"` // } - -type Coins []Coin - -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - ``` There are a few things to note. First, the `SendTx` includes a field for `Gas` and `Fee`. @@ -72,10 +79,9 @@ as it uses a different elliptic curve scheme which enables the public key to be 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. -## Next steps +## Plugins -1. Make your own [cryptocurrency using Basecoin plugins](example-counter.md) -1. Learn more about [plugin design](plugin-design.md) -1. See some [more example applications](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +Basecoin actually defines a second transaction type, the `AppTx`, +which enables the functionality to be extended via custom plugins. +To learn more about the `AppTx` and plugin system, see the [plugin design document](plugin-design.md). +To implement your first plugin, see [plugin tutorial](example-plugin.md). diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md index 1db3feed6e22..ec3189019490 100644 --- a/docs/guide/example-plugin.md +++ b/docs/guide/example-plugin.md @@ -409,11 +409,12 @@ This is a Merkle proof that the state is what we say it is. In a latter [tutorial on Interblockchain Communication](ibc.md), we'll put this proof to work! -## Conclusion +## Next Stpes In this tutorial we demonstrated how to create a new plugin and how to extend the basecoin CLI to activate the plugin on the blockchain and to send transactions to it. Hopefully by now you have some ideas for your own plugin, and feel comfortable implementing them. + In the [next tutorial](more-examples.md), we tour through some other plugin examples, adding features for minting new coins, voting, and changin the Tendermint validator set. -But first, you may want to learn a bit more about [the design of plugins](plugin-design.md) +But first, you may want to learn a bit more about [the design of the plugin system](plugin-design.md) diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md index 127c51960b0f..ff88bca77bab 100644 --- a/docs/guide/plugin-design.md +++ b/docs/guide/plugin-design.md @@ -64,9 +64,8 @@ and also to store arbitrary other information in the state. In this way, the functionality and state of a Basecoin-derrived cryptocurrency can be greatly extended. One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! +## Examples -## Next steps - -1. Examples of [Basecoin plugins](more-examples.md) -1. Learn how to use [InterBlockchain Communication (IBC)](ibc.md) -1. [Deploy testnets](deployment.md) running your basecoin application. +To get started with plugins, see [the example-plugin tutorial](example-plugin.md). +For more examples, see [the advanced plugin tutorial](more-examples.md). +If you're really brave, see the tutorial on [implementing Interblockchain Communication as a plugin](ibc.md). From 53f34f45ffc884aa39facae1181809d96a866551 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 19:54:28 -0500 Subject: [PATCH 8/9] docs: more examples --- docs/guide/more-examples.md | 40 ++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/docs/guide/more-examples.md b/docs/guide/more-examples.md index ad17c77a6626..7df37ca0be5f 100644 --- a/docs/guide/more-examples.md +++ b/docs/guide/more-examples.md @@ -1,25 +1,19 @@ # Plugin Examples -Now that we've seen how to use Basecoin, talked about the design, -and looked at how to implement a simple plugin, let's take a look at some more interesting examples. - -## Mintcoin - -Basecoin does not provide any functionality for adding new tokens to the system. -The state is endowed with tokens by a `genesis.json` file which is read once when the system is first started. -From there, tokens can be sent to other accounts, even new accounts, but it's impossible to add more tokens to the system. -For this, we need a plugin. - -The `mintcoin` plugin lets you register one or more accounts as "central bankers", -who can unilaterally issue more currency into the system. - -## Financial Instruments - -Sure, printing money and sending it is nice, but sometimes I don't fully trust the guy at the other end. Maybe we could add an escrow service? Or how about options for currency trading, since we support multiple currencies? No problem, this is also just a plugin away. Checkout our [trader application](./trader). - -**Running code, still WIP** - -## IBC - -Now, let's hook up your personal crypto-currency with the wide world of other currencies, in a distributed, proof-of-stake based exchange. Hard, you say? Well half the work is already done for you with the [IBC, InterBlockchain Communication, plugin](./ibc.md). Now, we just need to get cosmos up and running and time to go and trade. - +Now that we've seen [how to write a simple plugin](example-plugin.md) +and taken a look at [how the plugin system is designed](plugin-design.md), +it's time for some more advanced examples. + +For now, most examples are contained in the `github.com/tendermint/basecoin-examples` repository. +In particular, we have the following: + +1. [Mintcoin][0] - a plugin for issuing new Basecoin tokens +2. [Trader][1] - a plugin for adding escrow and options features to Basecoin +3. [Stakecoin][2] - a plugin for bonding and unbonding Tendermint validators and updating the validator set accordingly +4. [PayToVote][3] - a plugin for creating issues and voting on them +5. [IBC][4] - a plugin for facilitating InterBlockchain Communication +[0]: https://github.com/tendermint/basecoin-examples/tree/develop/mintcoin +[1]: https://github.com/tendermint/basecoin-examples/tree/develop/trader +[2]: https://github.com/tendermint/basecoin-examples/tree/develop/stake +[3]: https://github.com/tendermint/basecoin-examples/tree/develop/paytovote +[4]: ibc.md From b3834bc5d079a941070e5b7927825fb976bfbf27 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Tue, 7 Feb 2017 20:35:43 -0500 Subject: [PATCH 9/9] docs: update ibc --- cmd/commands/ibc.go | 3 -- demo/clean.sh | 2 +- .../{priv_validator.json => key.json} | 5 --- .../{priv_validator.json => key.json} | 5 --- demo/start.sh | 25 +++++++------ docs/guide/ibc.md | 35 +++++++++++++------ 6 files changed, 39 insertions(+), 36 deletions(-) rename demo/data/chain1/basecoin/{priv_validator.json => key.json} (74%) rename demo/data/chain2/basecoin/{priv_validator.json => key.json} (74%) diff --git a/cmd/commands/ibc.go b/cmd/commands/ibc.go index 24274331958c..f8d0e0b61972 100644 --- a/cmd/commands/ibc.go +++ b/cmd/commands/ibc.go @@ -142,9 +142,6 @@ var ( IbcPacketTxCmd = cli.Command{ Name: "packet", Usage: "Send a new packet via IBC", - Flags: []cli.Flag{ - // - }, Subcommands: []cli.Command{ IbcPacketCreateTx, IbcPacketPostTx, diff --git a/demo/clean.sh b/demo/clean.sh index e2d519337d9b..7d189232125d 100644 --- a/demo/clean.sh +++ b/demo/clean.sh @@ -1,6 +1,6 @@ #! /bin/bash -killall -9 basecoin tendermint +killall -9 adam tendermint TMROOT=./data/chain1/tendermint tendermint unsafe_reset_all TMROOT=./data/chain2/tendermint tendermint unsafe_reset_all diff --git a/demo/data/chain1/basecoin/priv_validator.json b/demo/data/chain1/basecoin/key.json similarity index 74% rename from demo/data/chain1/basecoin/priv_validator.json rename to demo/data/chain1/basecoin/key.json index 15d791924071..e610ba89d2aa 100644 --- a/demo/data/chain1/basecoin/priv_validator.json +++ b/demo/data/chain1/basecoin/key.json @@ -1,10 +1,5 @@ { "address": "D397BC62B435F3CF50570FBAB4340FE52C60858F", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, "priv_key": [ 1, "39E75AA1CF7BC710585977EFC375CD1730519186BD231478C339F2819C3C26E7B3588BDC92015ED3CDB6F57A86379E8C79A7111063610B7E625487C76496F4DF" diff --git a/demo/data/chain2/basecoin/priv_validator.json b/demo/data/chain2/basecoin/key.json similarity index 74% rename from demo/data/chain2/basecoin/priv_validator.json rename to demo/data/chain2/basecoin/key.json index 8f2eccadebce..90761696deeb 100644 --- a/demo/data/chain2/basecoin/priv_validator.json +++ b/demo/data/chain2/basecoin/key.json @@ -1,10 +1,5 @@ { "address": "053BA0F19616AFF975C8756A2CBFF04F408B4D47", - "last_height": 0, - "last_round": 0, - "last_signature": null, - "last_signbytes": "", - "last_step": 0, "priv_key": [ 1, "22920C428043D869987F253D7C9B2305E7010642C40CE88A52C9F6CE5ACC42080628C8E6C2D50B15764B443394E06C6A64F3082CE966A2A8C1A55A4D63D0FC5D" diff --git a/demo/start.sh b/demo/start.sh index da617910fdf9..b856852deac7 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -18,19 +18,19 @@ echo "CHAIN_ID1: $CHAIN_ID1" echo "CHAIN_ID2: $CHAIN_ID2" # make reusable chain flags -CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" -CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json" +CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657" echo "" echo "... starting chains" echo "" # start the first node TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +adam start --dir ./data/chain1/basecoin &> chain1_basecoin.log & # start the second node TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & echo "" echo "... waiting for chains to start" @@ -40,20 +40,20 @@ sleep 10 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json echo "" echo "... creating egress packet on chain1" echo "" # create a packet on chain1 destined for chain2 PAYLOAD="DEADBEEF" #TODO -basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 +adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload $PAYLOAD --sequence 1 echo "" echo "... querying for packet data" echo "" # query for the packet data and proof -QUERY_RESULT=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +QUERY_RESULT=$(adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) HEIGHT=$(echo $QUERY_RESULT | jq .height) PACKET=$(echo $QUERY_RESULT | jq .value) PROOF=$(echo $QUERY_RESULT | jq .proof) @@ -75,7 +75,7 @@ echo "" echo "... querying for block data" echo "" # get the header and commit for the height -HEADER_AND_COMMIT=$(basecoin block $HEIGHT) +HEADER_AND_COMMIT=$(adam block $HEIGHT) HEADER=$(echo $HEADER_AND_COMMIT | jq .hex.header) HEADER=$(removeQuotes $HEADER) COMMIT=$(echo $HEADER_AND_COMMIT | jq .hex.commit) @@ -89,16 +89,19 @@ echo "" echo "... updating state of chain1 on chain2" echo "" # update the state of chain1 on chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT +adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x$HEADER --commit 0x$COMMIT echo "" echo "... posting packet from chain1 on chain2" echo "" # post the packet from chain1 to chain2 -basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF +adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height $((HEIGHT + 1)) --packet 0x$PACKET --proof 0x$PROOF echo "" echo "... checking if the packet is present on chain2" echo "" # query for the packet on chain2 ! -basecoin query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 +adam query --node tcp://localhost:36657 ibc,ingress,test_chain_2,test_chain_1,1 + +echo "" +echo "DONE!" diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 96022310f362..fe98941c17fb 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -9,6 +9,8 @@ and here we'll show you how to use the Basecoin IBC-plugin to send a packet of d Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/plugin-design.md) and with the [Basecoin CLI](/docs/guide/basecoin-basics), but we'll explain how IBC works. +You may also want to see the tutorials on [a simple example plugin](example-plugin.md) +and the list of [more advanced plugins](more-examples.md). 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. @@ -179,7 +181,10 @@ Now that we have all the background knowledge, let's actually walk through the t Make sure you have installed [tendermint](https://tendermint.com/intro/getting-started/download) and -[basecoin](/docs/guide/install.md). +[adam](/docs/guide/install.md). + +`adam` is the name for the program that will become the Cosmos Hub. +We call it Adam because it's the first blockchain in [the Cosmos Network](https://cosmos.network). Now let's start the two blockchains. In this tutorial, each chain will have only a single validator, @@ -196,14 +201,14 @@ We can start the two chains as follows: ``` TMROOT=./data/chain1/tendermint tendermint node &> chain1_tendermint.log & -basecoin start --ibc-plugin --dir ./data/chain1/basecoin &> chain1_basecoin.log & +adam start --dir ./data/chain1/basecoin &> chain1_adam.log & ``` and ``` TMROOT=./data/chain2/tendermint tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -basecoin start --address tcp://localhost:36658 --ibc-plugin --dir ./data/chain2/basecoin &> chain2_basecoin.log & +adam start --address tcp://localhost:36658 --dir ./data/chain2/basecoin &> chain2_basecoin.log & ``` Note how we refer to the relevant data directories. Also note how we have to set the various addresses for the second node so as not to conflict with the first. @@ -225,27 +230,27 @@ For the sake of convenience, let's first set some environment variables: export CHAIN_ID1=test_chain_1 export CHAIN_ID2=test_chain_2 -export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/priv_validator.json" -export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/priv_validator.json --node tcp://localhost:36657" +export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/basecoin/key.json" +export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/basecoin/key.json --node tcp://localhost:36657" ``` Let's start by registering `test_chain_1` on `test_chain_2`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json +adam tx ibc --amount 10 $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/tendermint/genesis.json ``` Now we can create the outgoing packet on `test_chain_1`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +adam tx ibc --amount 10 $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 ``` Note our payload is just `DEADBEEF`. Now that the packet is committed in the chain, let's get some proof by querying: ``` -basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 +adam query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1 ``` The result contains the latest height, a value (ie. the hex-encoded binary serialization of our packet), @@ -256,7 +261,7 @@ We'll need a recent block header and a set of commit signatures. Fortunately, we can get them with the `block` command: ``` -basecoin block +adam block ``` where `` is the height returned in the previous query. @@ -266,7 +271,7 @@ The former is used as input for later commands; the latter is human-readable, so Let's send this updated information about `test_chain_1` to `test_chain_2`: ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x +adam tx ibc --amount 10 $CHAIN_FLAGS2 update --header 0x
--commit 0x ``` where `
` and `` are the hex-encoded header and commit returned by the previous `block` command. @@ -276,7 +281,7 @@ along with proof the packet was committed on `test_chain_1`. Since `test_chain_2 of `test_chain_1`, it will be able to verify the proof! ``` -basecoin ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +adam tx ibc --amount 10 $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x ``` Here, `` is one greater than the height retuned by the previous `query` command, and `` and `` are the @@ -286,3 +291,11 @@ Tada! ## 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 (ie. 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!