From 0f45f02dac6f609bbd4a561b5cc5dd6ed2eed53a Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 27 Apr 2017 00:51:44 -0400 Subject: [PATCH 1/4] guide refactor --- docs/guide/basecoin-basics.md | 100 ++++++++- docs/guide/basecoin-design.md | 95 -------- docs/guide/basecoin-plugins.md | 162 ++++++++++++++ docs/guide/basecoin-tool.md | 11 + docs/guide/deployment.md | 8 - docs/guide/example-plugin.md | 389 --------------------------------- docs/guide/more-examples.md | 14 -- docs/guide/plugin-design.md | 79 ------- 8 files changed, 263 insertions(+), 595 deletions(-) delete mode 100644 docs/guide/basecoin-design.md create mode 100644 docs/guide/basecoin-plugins.md delete mode 100644 docs/guide/deployment.md delete mode 100644 docs/guide/example-plugin.md delete mode 100644 docs/guide/more-examples.md delete mode 100644 docs/guide/plugin-design.md diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index 9ecb08c44..af3cd8308 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -1,7 +1,8 @@ # 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. +how to send transactions between accounts using the `basecoin` tool, +and what is happening under the hood. ## Install @@ -80,14 +81,93 @@ basecoin tx send --to 0x1B1BE55F969F54064628A63B9559E7C21C925165 --from key2.jso See `basecoin tx send --help` for additional details. -## Plugins +For a better understanding of the options, it helps to understand the underlying data structures. -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. +## Accounts -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) +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. + +```golang +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 under the key `base/a/
`, where `
` is the address of the account. +Typically, the address of the account is the 20-byte `RIPEMD160` hash of the public key, but other formats are acceptable as well, +as defined in the [tendermint crypto library](https://github.com/tendermint/go-crypto). +The Merkle tree used in Basecoin is a balanced, binary search tree, which we call an [IAVL tree](https://github.com/tendermint/go-merkle). + +## Transactions + +Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts. +The `SendTx` takes a list of inputs and a list of outputs, +and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. +The `SendTx` is structured as follows: + +```golang +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"` // +} +``` + +Note the `SendTx` includes a field for `Gas` and `Fee`. +The `Gas` limits the total amount of computation that can be done by the transaction, +while the `Fee` refers to the total amount paid in fees. +This is slightly different from Ethereum's concept of `Gas` and `GasPrice`, +where `Fee = Gas x GasPrice`. In Basecoin, the `Gas` and `Fee` are independent, +and the `GasPrice` is implicit. + +In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering +of transactions, like in bitcoin. And the `Gas` is meant to be used by the application +plugin to control its execution. There is currently no means to pass `Fee` information +to the Tendermint validators, but it will come soon... + +Note also that the `PubKey` only needs to be sent for `Sequence == 0`. +After that, it is stored under the account in the Merkle tree and subsequent transactions can exclude it, +using only the `Address` to refer to the sender. Ethereum does not require public keys to be sent in transactions +as it uses a different elliptic curve scheme which enables the public key to be derived from the signature itself. + +Finally, note that the use of multiple inputs and multiple outputs allows us to send many +different types of tokens between many different accounts at once in an atomic transaction. +Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple +inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of +coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction. + +## Conclusion + +In this guide, we introduced the `basecoin` tool, demonstrated how to use it to send tokens between accounts, +and discussed the underlying data types for accounts and transactions, specifically the `Account` and the `SendTx`. +In the [next guide](basecoin-plugins.md), we introduce the basecoin plugin system, which uses a new transaction type, the `AppTx`, +to extend the functionality of the Basecoin system with arbitrary logic. diff --git a/docs/guide/basecoin-design.md b/docs/guide/basecoin-design.md deleted file mode 100644 index a9bf5d503..000000000 --- a/docs/guide/basecoin-design.md +++ /dev/null @@ -1,95 +0,0 @@ -# Basecoin Design - -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. - -## 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. - -```golang -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, -In particular, an account is stored in the Merkle tree under the key `base/a/
`, -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 - -Basecoin defines a simple transaction type, the `SendTx`, which allows tokens to be sent to other accounts. -The `SendTx` takes a list of inputs and a list of outputs, -and transfers all the tokens listed in the inputs from their corresponding accounts to the accounts listed in the output. -The `SendTx` is structured as follows: - -```golang -type SendTx struct { - 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"` // -} -``` - -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. - -In Tendermint, the `Fee` is meant to be used by the validators to inform the ordering -of transactions, like in bitcoin. And the `Gas` is meant to be used by the application -plugin to control its execution. There is currently no means to pass `Fee` information -to the Tendermint validators, but it will come soon... - -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 derived from the signature itself. - -Finally, note that the use of multiple inputs and multiple outputs allows us to send many -different types of tokens between many different accounts at once in an atomic transaction. -Thus, the `SendTx` can serve as a basic unit of decentralized exchange. When using multiple -inputs and outputs, you must make sure that the sum of coins of the inputs equals the sum of -coins of the outputs (no creating money), and that all accounts that provide inputs have signed the transaction. - -## Plugins - -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/basecoin-plugins.md b/docs/guide/basecoin-plugins.md new file mode 100644 index 000000000..cc78d246a --- /dev/null +++ b/docs/guide/basecoin-plugins.md @@ -0,0 +1,162 @@ +# Basecoin Plugins + +In the [previous guide](basecoin-basics.md), +we saw how to use the `basecoin` tool to start a blockchain and send transactions. +We also learned about `Account` and `SendTx`, the basic data types giving us a multi-asset cryptocurrency. +Here, we will demonstrate how to extend the `basecoin` tool to use another transaction type, the `AppTx`, +to send data to a custom plugin. In this case we use a simple plugin that takes a single boolean argument, +and only accept the transaction if the argument is set to `true`. + +## Example Plugin + +The design of the `basecoin` tool makes it easy to extend for custom functionality. +To see what this looks like, install the `example-plugin` tool: + +``` +cd $GOPATH/src/github.com/tendermint/basecoin +go install ./docs/guide/src/example-plugin +``` + +The `example-plugin` tool is just like the `basecoin` tool. +They both use the same library of commands, including one for signing and broadcasting `SendTx`. +See `example-plugin --help` for details. + +A new blockchain can be initialized and started just like with `basecoin`: + +``` +example-plugin init +example-plugin start +``` + +The default files are stored in `~/.basecoin-example-plugin`. +In another window, we can send a `SendTx` like we are used to: + +``` +example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 1mycoin +``` + +But the `example-plugin` tool has an additional command, `example-plugin tx example`, +which crafts an `AppTx` specifically for our example plugin. +This command lets you send a single boolean argument: + +``` +example-plugin tx example --amount 1mycoin +example-plugin tx example --amount 1mycoin --valid +``` + +The first transaction is rejected by the plugin because it was not marked as valid, while the second transaction passes. +We can build plugins that take many arguments of different types, and easily extend the tool to accomodate them. +Of course, we can also expose queries on our plugin: + +``` +example-plugin query ExamplePlugin.State +``` + +Note the `"value":"0101"`. This is the serialized form of the state, +which contains only an integer, the number of valid transactions. +If we send another transaction, and then query again, we will see the value increment: + +``` +example-plugin tx example --valid --amount 1mycoin +example-plugin query ExamplePlugin.State +``` + +The value should now be `0102`, because we sent a second valid transaction. +Notice how the result of the query comes with a proof. +This is a Merkle proof that the state is what we say it is. +In a latter [guide on InterBlockchain Communication](ibc.md), +we'll put this proof to work! + + +Now, before we implement our own plugin and tooling, it helps to understand the `AppTx` and the design of the plugin system. + +## AppTx + +The `AppTx` is similar to the `SendTx`, but instead of sending coins from inputs to outputs, +it sends coins from one input to a plugin, and can also send some data. + +```golang +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 +} +``` + +The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins. +The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction, +and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing. + +Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`. +It also includes a single `TxInput`, which specifies the sender of the transaction, +and some coins that can be forwarded to the plugin as well. + +## Plugins + +A plugin is simply a Go package that implements the `Plugin` interface: + +```golang +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, hash []byte, header *abci.Header) + EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock) +} + +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 `Data` from the `AppTx` is passed in as the `txBytes`, +while the `Input` from the `AppTx` is used to populate the `CallContext`. + +Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data. +By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly, +and also to store arbitrary other information in the state. +In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended. +One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! + +For details on how to initialize the state using `SetOption`, see the [guide to using the basecoin tool](basecoin-tool.md#genesis). + + +## Implement your own + +To implement your own plugin and tooling, make a copy of `docs/guide/src/example-plugin`, +and modify the code accordingly. Here, we will briefly describe the design and the changes to be made, +but see the code for more details. + +First is the `main.go`, which drives the program. It can be left alone, but you should change any occurences of `example-plugin` +to whatever your plugin tool is going to be called. + +Next is the `cmd.go`. This is where we extend the tool with any new commands and flags we need to send transactions to our plugin. +Note the `init()` function, where we register a new transaction subcommand with `RegisterTxSubcommand`, +and where we load the plugin into the basecoin app with `RegisterStartPlugin`. + +Finally is the `plugin.go`, where we provide an implementation of the `Plugin` interface. +The most important part of the implementation is the `RunTx` method, which determines the meaning of the data +sent along in the `AppTx`. In our example, we define a new transaction type, the `ExamplePluginTx`, which +we expect to be encoded in the `AppTx.Data`, and thus to be decoded in the `RunTx` method, and used to update the plugin state. + +For more examples and inspiration, see our [repository of example plugins](https://github.com/tendermint/basecoin-examples). + +## Conclusion + +In this guide, we demonstrated how to create a new plugin and how to extend the +`basecoin` tool to start a blockchain with the plugin enabled and send transactions to it. +In the next guide, we introduce a [plugin for Inter Blockchain Communication](ibc.md), +which allows us to publish proofs of the state of one blockchain to another, +and thus to transfer tokens and data between them. diff --git a/docs/guide/basecoin-tool.md b/docs/guide/basecoin-tool.md index 8af94cdee..52f42d832 100644 --- a/docs/guide/basecoin-tool.md +++ b/docs/guide/basecoin-tool.md @@ -140,3 +140,14 @@ You can reset all blockchain data by running: ``` basecoin unsafe_reset_all ``` + + +# Genesis + +Any required plugin initialization should be constructed using `SetOption` on genesis. +When starting a new chain for the first time, `SetOption` will be called for each item the genesis file. +Within genesis.json file entries are made in the format: `"/", ""`, where `` is the plugin name, +and `` and `` are the strings passed into the plugin SetOption function. +This function is intended to be used to set plugin specific information such +as the plugin state. + diff --git a/docs/guide/deployment.md b/docs/guide/deployment.md deleted file mode 100644 index 74e954a25..000000000 --- a/docs/guide/deployment.md +++ /dev/null @@ -1,8 +0,0 @@ -## Deployment - -Up until this point, we have only been testing the code as a blockchain with a single validator node running locally. -This is nice for developing, but it's not a real distributed application yet. - -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. -We do this using the [mintnet-kubernetes tool](https://github.com/tendermint/mintnet-kubernetes). diff --git a/docs/guide/example-plugin.md b/docs/guide/example-plugin.md deleted file mode 100644 index a4592b355..000000000 --- a/docs/guide/example-plugin.md +++ /dev/null @@ -1,389 +0,0 @@ -# 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 -``` - -### main.go - -The `main.go` is very simple and does not need to be changed: - -```golang -func main() { - //Initialize example-plugin root command - var RootCmd = &cobra.Command{ - Use: "example-plugin", - Short: "example-plugin usage description", - } - - //Add the default basecoin commands to the root command - RootCmd.AddCommand( - commands.InitCmd, - commands.StartCmd, - commands.TxCmd, - commands.QueryCmd, - commands.KeyCmd, - commands.VerifyCmd, - commands.BlockCmd, - commands.AccountCmd, - commands.UnsafeResetAllCmd, - ) - - //Run the root command - commands.ExecuteWithDebug(RootCmd) -} -``` - -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. - -### cmd.go - -First we define the new CLI command and associated flag variables. - -```golang -var ( - //CLI Flags - validFlag bool - - //CLI Plugin Commands - ExamplePluginTxCmd = &cobra.Command{ - Use: "example", - Short: "Create, sign, and broadcast a transaction to the example plugin", - RunE: examplePluginTxCmd, - } -) -``` - -Next within the `init` function we register our plugin's flags and register our -custom plugin command with the root command. This creates a new subcommand -under `tx` (defined below), and ensures the plugin is activated when we start -the app. - -```golang -func init() { - - //Set the Plugin Flags - ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") - - //Register a plugin specific CLI command as a subcommand of the tx command - commands.RegisterTxSubcommand(ExamplePluginTxCmd) - - //Register the example with basecoin at start - commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() }) -} -``` - -We now define the actual function which is called by our CLI command. - -```golang -func examplePluginTxCmd(cmd *cobra.Command, args []string) error { - exampleTx := ExamplePluginTx{validFlag} - exampleTxBytes := wire.BinaryBytes(exampleTx) - return commands.AppTx("example-plugin", exampleTxBytes) -} -``` - -Our function is a simple command with one boolean flag. However, it actually -inherits the persistent flags from the Basecoin framework. These persistent -flags use pointers to these variables stored in `cmd/commands/tx.go`: - -```golang -var ( - //persistent flags - txNodeFlag string - amountFlag string - fromFlag string - seqFlag int - gasFlag int - feeFlag string - chainIDFlag string - - //non-persistent flags - toFlag string - dataFlag string - nameFlag string -) -``` - -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: - -``` -Create, sign, and broadcast a transaction to the example plugin - -Usage: - example-plugin tx example [flags] - -Flags: - --valid Set this to make transaction valid - -Global Flags: - --amount string Coins to send in transaction of the format ,,... (eg: 1btc,2gold,5silver}, - --chain_id string ID of the chain for replay protection (default "test_chain_id") - --fee string Coins for the transaction fee of the format - --from string Path to a private key to sign the transaction (default "key.json") - --gas int The amount of gas for the transaction - --node string Tendermint RPC address (default "tcp://localhost:46657") - --sequence int Sequence number for the account (-1 to autocalculate}, (default -1) -``` - -Cool, eh? - -Before we move on to `plugin.go`, let's look at the `examplePluginTxCmd` -function in `cmd.go`: - -```golang -func examplePluginTxCmd(cmd *cobra.Command, args []string) { - exampleTx := ExamplePluginTx{validFlag} - exampleTxBytes := wire.BinaryBytes(exampleTx) - commands.AppTx("example-plugin", exampleTxBytes) -} -``` - -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. - -### plugin.go - -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 methods. Here's what's -relevant for us: - -```golang -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 txBytes using go-wire. Attempt to write the txBytes to the variable - // tx, if the txBytes have not been properly encoded from a ExamplePluginTx - // struct wire will produce an error. - var tx ExamplePluginTx - err := wire.ReadBinaryBytes(txBytes, &tx) - if err != nil { - return abci.ErrBaseEncodingError.AppendLog("Error decoding tx: " + err.Error()) - } - - // Perform Transaction Validation - if !tx.Valid { - return abci.ErrInternalError.AppendLog("Valid must be true") - } - - // Load PluginState - var pluginState ExamplePluginState - stateBytes := store.Get(ep.StateKey()) - // If the state does not exist, stateBytes will be initialized - // as an empty byte array with length of zero - if len(stateBytes) > 0 { - err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire - if err != nil { - return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error()) - } - } - - //App Logic - pluginState.Counter += 1 - - // Save PluginState - store.Set(ep.StateKey(), wire.BinaryBytes(pluginState)) - - return abci.OK -} -``` - -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: - -```golang -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 correctly, we can now check if it's valid: - -```golang -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: - -```golang -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`. -Also note, that we do nothing if there is no existing state data. Is that a bug? No, we just make -use of Go's variable initialization, that `pluginState` will contain a `Counter` value of 0. -If your app needs more initialization than empty variables, then do this logic here in an `else` block. - -Finally, we can update the state's `Counter`, and save the state back to the store: - -```golang -//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 - -First, initialize the new blockchain with - -``` -basecoin init -``` - -If you've already run a basecoin blockchain, reset the data with - -``` -basecoin unsafe_reset_all -``` - -To start the blockchain with your new plugin, simply run - -``` -example-plugin start -``` - -In another window, we can try sending some transactions: - -``` -example-plugin tx send --to 0x1DA7C74F9C219229FD54CC9F7386D5A3839F0090 --amount 100mycoin -``` - -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 1mycoin -``` - -The transaction is invalid! That's because we didn't specify the `--valid` flag: - -``` -example-plugin tx example --valid --amount 1mycoin -``` - -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 1mycoin -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! - -## Next Steps - -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, -addin mple-plugin query ExamplePlugin.Statefeatures for minting new coins, voting, and changing the Tendermint validator set. -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/more-examples.md b/docs/guide/more-examples.md deleted file mode 100644 index 1fc1b545c..000000000 --- a/docs/guide/more-examples.md +++ /dev/null @@ -1,14 +0,0 @@ -# Plugin Examples - -Now that we've seen [how to write a simple plugin](/docs/guide/example-plugin.md) -and taken a look at [how the plugin system is designed](/docs/guide/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](https://github.com/tendermint/basecoin-examples/tree/develop/mintcoin) - a plugin for issuing new Basecoin tokens -2. [Trader](https://github.com/tendermint/basecoin-examples/tree/develop/trader) - a plugin for adding escrow and options features to Basecoin -3. [Stakecoin](https://github.com/tendermint/basecoin-examples/tree/develop/stake) - a plugin for bonding and unbonding Tendermint validators and updating the validator set accordingly -4. [PayToVote](https://github.com/tendermint/basecoin-examples/tree/develop/paytovote) - a plugin for creating issues and voting on them -5. [IBC](/docs/guide/ibc.md) - a plugin for facilitating InterBlockchain Communication diff --git a/docs/guide/plugin-design.md b/docs/guide/plugin-design.md deleted file mode 100644 index e8e7dc92f..000000000 --- a/docs/guide/plugin-design.md +++ /dev/null @@ -1,79 +0,0 @@ -# Basecoin Plugins - -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. - - -## AppTx - -In addition to the `SendTx`, Basecoin also defines another transaction type, the `AppTx`: - -```golang -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 -} -``` - -The `AppTx` enables Basecoin to be extended with arbitrary additional functionality through the use of plugins. -The `Name` field in the `AppTx` refers to the particular plugin which should process the transaction, -and the `Data` field of the `AppTx` is the data to be forwarded to the plugin for processing. - -Note the `AppTx` also has a `Gas` and `Fee`, with the same meaning as for the `SendTx`. -It also includes a single `TxInput`, which specifies the sender of the transaction, -and some coins that can be forwarded to the plugin as well. - -## Plugins - -A plugin is simply a Go package that implements the `Plugin` interface: - -```golang -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, hash []byte, header *abci.Header) - EndBlock(store KVStore, height uint64) (res abci.ResponseEndBlock) -} - -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 `Data` from the `AppTx` is passed in as the `txBytes`, -while the `Input` from the `AppTx` is used to populate the `CallContext`. - -Note that `RunTx` also takes a `KVStore` - this is an abstraction for the underlying Merkle tree which stores the account data. -By passing this to the plugin, we enable plugins to update accounts in the Basecoin state directly, -and also to store arbitrary other information in the state. -In this way, the functionality and state of a Basecoin-derived cryptocurrency can be greatly extended. -One could imagine going so far as to implement the Ethereum Virtual Machine as a plugin! - -Any required plugin initialization should be constructed within `SetOption`. -`SetOption` may be called during genesis of basecoin and can be used to set -initial plugin parameters. Within genesis.json file entries are made in -the format: `"/", ""`, where `` is the plugin name, -and `` and `` are the strings passed into the plugin SetOption function. -This function is intended to be used to set plugin specific information such -as the plugin state. - -## Examples - -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 2e83795fc1eb46cce9a5c4c1f9f893e12a4703eb Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 27 Apr 2017 00:54:56 -0400 Subject: [PATCH 2/4] update readme --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0c15ed057..d432c2081 100644 --- a/README.md +++ b/README.md @@ -29,12 +29,9 @@ See the [install guide](/docs/guide/install.md) for more details. ## Guide 1. Getting started with the [Basecoin basics](/docs/guide/basecoin-basics.md) -1. Learn more about [Basecoin's design](/docs/guide/basecoin-design.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. Learning to [use the plugin system](/docs/guide/basecoin-plugins.md) 1. More features of the [Basecoin tool](/docs/guide/basecoin-tool.md) 1. Learn how to use [InterBlockchain Communication (IBC)](/docs/guide/ibc.md) -1. [Deploy testnets](/docs/guide/deployment.md) running your basecoin application. +To deploy a testnet, see our [repository of deployment tools](https://github.com/tendermint/tools). From f52247908b84052b0358ec0778599cb853307dc4 Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 27 Apr 2017 01:38:38 -0400 Subject: [PATCH 3/4] fix up ibc tutorial --- demo/start.sh | 6 ++-- docs/guide/basecoin-basics.md | 10 +++--- docs/guide/ibc.md | 51 +++++++++++++++------------- docs/guide/src/example-plugin/cmd.go | 2 ++ 4 files changed, 37 insertions(+), 32 deletions(-) diff --git a/demo/start.sh b/demo/start.sh index d7d9659dc..e456b9bbc 100644 --- a/demo/start.sh +++ b/demo/start.sh @@ -82,11 +82,11 @@ echo "" echo "... starting chains" echo "" # start the first node -TMROOT=./data/chain1 tendermint node --skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log & +TMROOT=$BCHOME1 tendermint node --skip_upnp --log_level=info &> $LOG_DIR/chain1_tendermint.log & BCHOME=$BCHOME1 basecoin start --without-tendermint &> $LOG_DIR/chain1_basecoin.log & # start the second node -TMROOT=./data/chain2 tendermint node --skip_upnp --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log & +TMROOT=$BCHOME2 tendermint node --skip_upnp --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> $LOG_DIR/chain2_tendermint.log & BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> $LOG_DIR/chain2_basecoin.log & echo "" @@ -103,7 +103,7 @@ sleep 3 echo "... registering chain1 on chain2" echo "" # register chain1 on chain2 -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis ./data/chain1/genesis.json +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json echo "" echo "... creating egress packet on chain1" diff --git a/docs/guide/basecoin-basics.md b/docs/guide/basecoin-basics.md index af3cd8308..9e65df1d6 100644 --- a/docs/guide/basecoin-basics.md +++ b/docs/guide/basecoin-basics.md @@ -37,10 +37,10 @@ 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 `key.json`. -We also included the private key for another account, in `key2.json`. +Now we are ready to send some transactions. First, open another window. +If you take a look at the `~/.basecoin/genesis.json` file, you will see one account listed under the `app_options`. +This account corresponds to the private key in `~/.basecoin/key.json`. +We also included the private key for another account, in `~/.basecoin/key2.json`. Let's check the balance of these two accounts: @@ -86,7 +86,7 @@ For a better understanding of the options, it helps to understand the underlying ## Accounts The Basecoin state consists entirely of a set of accounts. -Each account contains an ED25519 public key, +Each account contains a public key, a balance in many different coin denominations, and a strictly increasing sequence number for replay protection. This type of account was directly inspired by accounts in Ethereum, diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index abbf0908f..2b7279245 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -7,10 +7,8 @@ The simplest example of using the IBC protocol is to send a data packet from one 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. -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). +Please note, this tutorial assumes you are familiar with [Basecoin plugins](/docs/guide/basecoin-plugins.md), +but we'll explain how IBC works. You may also want to see [our repository of example plugins](https://github.com/tendermint/basecoin-examples). The IBC plugin defines a new set of transactions as subtypes of the `AppTx`. The plugin's functionality is accessed by setting the `AppTx.Name` field to `"IBC"`, @@ -181,6 +179,7 @@ Make sure you have installed [basecoin](/docs/guide/install.md). `basecoin` is a framework for creating new cryptocurrency applications. +It comes with an `IBC` plugin enabled by default. Now let's start the two blockchains. In this tutorial, each chain will have only a single validator, @@ -192,22 +191,38 @@ cd $GOPATH/src/github.com/tendermint/basecoin/demo ``` The relevant data is now in the `data` directory. +Before we begin, let's set some environment variables for convenience: +``` +export BCHOME="." +BCHOME1="./data/chain1" +BCHOME2="./data/chain2" + +export CHAIN_ID1=test_chain_1 +export CHAIN_ID2=test_chain_2 + +CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from $BCHOME1/key.json" +CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from $BCHOME2/key.json --node tcp://localhost:36657" +``` + +In previous examples, we started basecoin in-process with tendermint. +Here, we will run them in different processes, using the `--without-tendermint` flag, +as described in the [guide to the basecoin tool](basecoin-tool.md). We can start the two chains as follows: ``` -TMROOT=./data/chain1 tendermint node &> chain1_tendermint.log & -BCHOME=./data/chain1 basecoin start --without-tendermint &> chain1_basecoin.log & +TMROOT=$BCHOME1 tendermint node --log_level=info &> chain1_tendermint.log & +BCHOME=$BCHOME1 basecoin start --without-tendermint &> chain1_basecoin.log & ``` and ``` -TMROOT=./data/chain2 tendermint node --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & -BCHOME=./data/chain2 basecoin start --without-tendermint --address tcp://localhost:36658 &> chain2_basecoin.log & +TMROOT=$BCHOME2 tendermint node --log_level=info --node_laddr tcp://localhost:36656 --rpc_laddr tcp://localhost:36657 --proxy_app tcp://localhost:36658 &> chain2_tendermint.log & +BCHOME=$BCHOME2 basecoin start --address tcp://localhost:36658 --without-tendermint &> chain2_basecoin.log & ``` -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. +Note how we refer to the relevant data directories, and how we set the various addresses for the second node so as not to conflict with the first. We can now check on the status of the two chains: @@ -220,28 +235,16 @@ If either command fails, the nodes may not have finished starting up. Wait a cou 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: +We begin by registering `test_chain_1` on `test_chain_2`: ``` -export CHAIN_ID1=test_chain_1 -export CHAIN_ID2=test_chain_2 - -export CHAIN_FLAGS1="--chain_id $CHAIN_ID1 --from ./data/chain1/key.json" -export CHAIN_FLAGS2="--chain_id $CHAIN_ID2 --from ./data/chain2/key.json --node tcp://localhost:36657" - -export BCHOME="." -``` - -Let's start by registering `test_chain_1` on `test_chain_2`: - -``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --chain_id $CHAIN_ID1 --genesis ./data/chain1/genesis.json +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 register --ibc_chain_id $CHAIN_ID1 --genesis $BCHOME1/genesis.json ``` Now we can create the outgoing packet on `test_chain_1`: ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --sequence 1 +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS1 packet create --ibc_from $CHAIN_ID1 --to $CHAIN_ID2 --type coin --payload 0xDEADBEEF --ibc_sequence 1 ``` Note our payload is just `DEADBEEF`. diff --git a/docs/guide/src/example-plugin/cmd.go b/docs/guide/src/example-plugin/cmd.go index b43917626..11a6f6bc0 100644 --- a/docs/guide/src/example-plugin/cmd.go +++ b/docs/guide/src/example-plugin/cmd.go @@ -24,6 +24,8 @@ var ( //Called during CLI initialization func init() { + commands.DefaultHome = ".basecoin-example-plugin" + //Set the Plugin Flags ExamplePluginTxCmd.Flags().BoolVar(&validFlag, "valid", false, "Set this to make transaction valid") From 0672f2990d1a63fee72413cd7c5f137437694edb Mon Sep 17 00:00:00 2001 From: Ethan Buchman Date: Thu, 27 Apr 2017 01:59:06 -0400 Subject: [PATCH 4/4] docs: use jq for ibc --- docs/guide/ibc.md | 38 ++++++++++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/docs/guide/ibc.md b/docs/guide/ibc.md index 2b7279245..71e196d01 100644 --- a/docs/guide/ibc.md +++ b/docs/guide/ibc.md @@ -181,6 +181,8 @@ Make sure you have installed `basecoin` is a framework for creating new cryptocurrency applications. It comes with an `IBC` plugin enabled by default. +You will also want to install the [jq](https://stedolan.github.io/jq/) for handling JSON at the command line. + 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. @@ -251,44 +253,60 @@ 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 +QUERY=$(basecoin query ibc,egress,$CHAIN_ID1,$CHAIN_ID2,1) +echo $QUERY ``` The result contains the latest height, a value (i.e. the hex-encoded binary serialization of our packet), and a proof (i.e. hex-encoded binary serialization of a list of nodes from the Merkle tree) that the value is in the Merkle tree. +We keep the result in the `QUERY` variable so we can easily reference subfields using the `jq` tool. 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 +BLOCK=$(basecoin block $(echo $QUERY | jq .height)) +echo $BLOCK ``` -where `` is the height returned in the previous query. +Here, we are passing `basecoin block` the `height` from our earlier query. Note the result contains both a hex-encoded and json-encoded version of the header and the commit. The former is used as input for later commands; the latter is human-readable, so you know what's going on! -Let's send this updated information about `test_chain_1` to `test_chain_2`: +Let's send this updated information about `test_chain_1` to `test_chain_2`. +First, output the header and commit for reference: + +``` +echo $BLOCK | jq .hex.header +echo $BLOCK | jq .hex.commit +``` + +And now forward those values to `test_chain_2`: ``` basecoin tx ibc --amount 10mycoin $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! +First, output the height, packet, and proof for reference: + ``` -basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --from $CHAIN_ID1 --height --packet 0x --proof 0x +echo $QUERY | jq .height +echo $QUERY | jq .value +echo $QUERY | jq .proof ``` -Here, `` is the height retuned by the previous `query` command, and `` and `` are the -`value` and `proof` returned in that same query. +And forward those values to `test_chain_2`: -Tada! +``` +basecoin tx ibc --amount 10mycoin $CHAIN_FLAGS2 packet post --ibc_from $CHAIN_ID1 --height --packet 0x --proof 0x +``` + +If the command does not return an error, then we have successfuly transfered data from `test_chain_1` to `test_chain_2`. Tada! ## Conclusion