doc edits

go basics doc update

exposed comment
This commit is contained in:
rigelrozanski 2017-02-13 01:57:21 -05:00 committed by Ethan Buchman
parent 1d8f59644f
commit c5f837c68e
10 changed files with 225 additions and 82 deletions

View File

@ -1,25 +1,71 @@
# Go Basics
This document is designed for developers new to the go language, especially experienced developers who are learning go for the purpose of using tendermint.
This document is designed for developers new to the go language, especially
experienced developers who are learning go for the purpose of using Tendermint.
Go is a rather simple language, which aims to produce fast, maintainable programs, while minimizing development effort. In order to speed up development, the go community has adopted quite a number of conventions, which are used in almost every open source project. The same way one rails dev can learn a new project quickly as they all have the same enforced layout, programming following these conventions allows for interoperability with much of the go tooling, and a much more fluid development experience.
Go is a rather simple language, which aims to produce fast, maintainable
programs, while minimizing development effort. In order to speed up
development, the go community has adopted quite a number of conventions, which
are used in almost every open source project. The same way one rails dev can
learn a new project quickly as they all have the same enforced layout,
programming following these conventions allows for interoperability with much
of the go tooling, and a much more fluid development experience.
First of all, you should read through [Effective Go](https://golang.org/doc/effective_go.html) to get a feel for the language and the constructs. And maybe pick up a book, read a tutorial, or do what you feel best to feel comfortable with the syntax.
First of all, you should read through [Effective
Go](https://golang.org/doc/effective_go.html) to get a feel for the language
and the constructs. And maybe pick up a book, read a tutorial, or do what you
feel best to feel comfortable with the syntax.
Second, you need to set up your go environment. In go, all code hangs out GOPATH. You don't have a separate root directory for each project. Pick a nice locations (like `$HOME/go`) and `export GOPATH` in your startup scripts (`.bashrc` or the like). Note that go compiles all programs to `$GOPATH/bin`, similarly PATH will need to be updated in the startup scripts. If your are editing `.bashrc` (typically found in HOME) you would add the following lines:
Second, you need to set up your go environment. In go, all code hangs out
GOPATH. You don't have a separate root directory for each project. Pick a nice
locations (like `$HOME/go`) and `export GOPATH` in your startup scripts
(`.bashrc` or the like). Note that go compiles all programs to `$GOPATH/bin`,
similarly PATH will need to be updated in the startup scripts. If your are
editing `.bashrc` (typically found in HOME) you would add the following lines:
```
export GOPATH=$HOME/go
export PATH=$PATH:$GOROOT/bin:$GOPATH/bin
```
Now, when you run `go get github.com/tendermint/basecoin`, this will create the directory `$GOPATH/src/github.com/tendermint/basecoin`, checkout the master branch with git, and try to compile if there are any scripts. All your repos will fit under GOPATH with a similar logic. Just pick good names for your github repos. If you put your code outside of GOPATH/src or have a path other than the url of the repo, you can expect errors. There are ways to do this, but quite complex and not worth the bother.
Now, when you run `go get github.com/tendermint/basecoin`, this will create the
directory `$GOPATH/src/github.com/tendermint/basecoin`, checkout the master
branch with git, and try to compile if there are any scripts. All your repos
will fit under GOPATH with a similar logic. Just pick good names for your
github repos. If you put your code outside of GOPATH/src or have a path other
than the url of the repo, you can expect errors. There are ways to do this,
but quite complex and not worth the bother.
Third, every repo in `$GOPATH/src` is checkout out of a version control system (commonly git), and you can go into those directories and manipulate them like any git repo (`git checkout develop`, `git pull`, `git remote set-url origin $MY_FORK`). `go get -u $REPO` is a nice convenience to do a `git pull` on the master branch and recompile if needed. If you work on develop, get used to using the git commands directly in these repos. [here](https://tendermint.com/docs/guides/contributing) are some more tips on using git with open source go projects with absolute dependencies such as tendermint.
Third, every repo in `$GOPATH/src` is checkout out of a version control system
(commonly git), and you can go into those directories and manipulate them like
any git repo (`git checkout develop`, `git pull`, `git remote set-url origin
$MY_FORK`). `go get -u $REPO` is a nice convenience to do a `git pull` on the
master branch and recompile if needed. If you work on develop, get used to
using the git commands directly in these repos.
[Here](https://tendermint.com/docs/guides/contributing) are some more tips on
using git with open source go projects with absolute dependencies such as
Tendermint.
Fourth, installing a go program is rather easy if you know what to do. First to note is all programs compiles with `go install` and end up in `$GOPATH/bin`. `go get` will checkout the repo, then try to `go install` it. Many repos are mainly a library that also export (one or more) commands, in these cases there is a subdir called `cmd`, with a different subdir for each command, using the command name as the directory name. To compile these commands, you can go something like `go install github.com/tendermint/basecoin/cmd/basecoin` or to compile all the commands `go install github.com/tendermint/basecoin/cmd/...` (... is a go tooling shortcut for all subdirs, like `*`).
Fourth, installing a go program is rather easy if you know what to do. First
to note is all programs compiles with `go install` and end up in `$GOPATH/bin`.
`go get` will checkout the repo, then try to `go install` it. Many repos are
mainly a library that also export (one or more) commands, in these cases there
is a subdir called `cmd`, with a different subdir for each command, using the
command name as the directory name. To compile these commands, you can go
something like `go install github.com/tendermint/basecoin/cmd/basecoin` or to
compile all the commands `go install github.com/tendermint/basecoin/cmd/...`
(... is a go tooling shortcut for all subdirs, like `*`).
Fifth, there isn't good dependency management built into go. By default, when compiling a go program which imports another repo, go will compile using the latest master branch, or whichever version you have checked out and located. This can cause serious issues, and there is tooling to do dependency management. As of go 1.6, the `vendor` directory is standard and a copy of a repo will be used rather than the repo under GOPATH. In order to create and maintain the code in the vendor directory, various tools have been created, with [glide](https://github.com/Masterminds/glide) being popular and in use in all the tendermint repos. In this case, `go install` is not enough. If you are working on code from the tendermint, you will usually want to do:
Fifth, there isn't good dependency management built into go. By default, when
compiling a go program which imports another repo, go will compile using the
latest master branch, or whichever version you have checked out and located.
This can cause serious issues, and there is tooling to do dependency
management. As of go 1.6, the `vendor` directory is standard and a copy of a
repo will be used rather than the repo under GOPATH. In order to create and
maintain the code in the vendor directory, various tools have been created,
with [glide](https://github.com/Masterminds/glide) being popular and in use in
all the Tendermint repos. In this case, `go install` is not enough. If you are
working on code from the Tendermint, you will usually want to do:
```
go get github.com/tendermint/$REPO
@ -29,6 +75,11 @@ make install
make test
```
`make get_vendor_deps` should update the vendor directory using glide, `make install` will compile all commands. `make test` is good to run the test suite and make sure things are working with your environment... failing tests are much easier to debug than a malfunctioning program.
`make get_vendor_deps` should update the vendor directory using glide, `make
install` will compile all commands. `make test` is good to run the test suite
and make sure things are working with your environment... failing tests are
much easier to debug than a malfunctioning program.
Okay, that's it, with this info you should be able to follow along and
trouble-shoot any issues you have with the rest of the guide.
Okay, that's it, with this info you should be able to follow along and trouble-shoot any issues you have with the rest of the guide.

View File

@ -1,13 +1,16 @@
# 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.
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).
You will also need to [install Tendermint](https://tendermint.com/intro/getting-started/download).
**Note** All code is on the 0.9 pre-release branch, you may have to [install tendermint from source](https://tendermint.com/docs/guides/install) until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions)
**Note** All code is on the 0.9 pre-release branch, you may have to
[install Tendermint from source](https://tendermint.com/docs/guides/install)
until 0.9 is released. (Make sure to add `git checkout develop` to the linked install instructions)
## Initialization
@ -18,7 +21,7 @@ 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
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):
```
@ -49,7 +52,8 @@ 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:
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
@ -71,7 +75,7 @@ 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.
`develop` branch of Tendermint for this to work.
## Send transactions

View File

@ -14,7 +14,7 @@ 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"`
@ -42,7 +42,7 @@ 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"`
@ -55,7 +55,7 @@ type TxInput struct {
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
PubKey crypto.PubKey `json:"pub_key"` // Is present if Sequence == 0
}
type TxOutput struct {
@ -71,15 +71,21 @@ 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...
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 derrived from the signature itself.
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.
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

View File

@ -1,7 +1,9 @@
## 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.
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.
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???

View File

@ -21,7 +21,7 @@ plugin.go
The `main.go` is very simple and does not need to be changed:
```
```golang
func main() {
app := cli.NewApp()
app.Name = "example-plugin"
@ -50,7 +50,7 @@ This is where the `cmd.go` comes in.
First, we register the plugin:
```
```golang
func init() {
commands.RegisterTxSubcommand(ExamplePluginTxCmd)
commands.RegisterStartPlugin("example-plugin", func() types.Plugin { return NewExamplePlugin() })
@ -61,7 +61,7 @@ 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:
```
```golang
var (
ExampleFlag = cli.BoolFlag{
Name: "valid",
@ -88,13 +88,13 @@ func cmdExamplePluginTx(c *cli.Context) error {
It's a simple command with one flag, which is just a boolean.
However, it actually inherits more flags from the Basecoin framework:
```
```golang
Flags: append(commands.TxFlags, ExampleFlag),
```
The `commands.TxFlags` is defined in `cmd/commands/tx.go`:
```
```golang
var TxFlags = []cli.Flag{
NodeFlag,
ChainIDFlag,
@ -144,7 +144,7 @@ Cool, eh?
Before we move on to `plugin.go`, let's look at the `cmdExamplePluginTx` function in `cmd.go`:
```
```golang
func cmdExamplePluginTx(c *cli.Context) error {
exampleFlag := c.Bool("valid")
exampleTx := ExamplePluginTx{exampleFlag}
@ -166,7 +166,7 @@ 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
}
@ -236,7 +236,7 @@ and then using the `RunTx` method to define how the transaction updates the stat
Let's break down `RunTx` in parts. First, we deserialize the transaction:
```
```golang
// Decode tx
var tx ExamplePluginTx
err := wire.ReadBinaryBytes(txBytes, &tx)
@ -250,9 +250,9 @@ 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:
If the transaction deserializes correctly, we can now check if it's valid:
```
```golang
// Validate tx
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
@ -264,7 +264,7 @@ Finally, we can update the state. In this example, the state simply counts how m
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
// Load PluginState
var pluginState ExamplePluginState
stateBytes := store.Get(ep.StateKey())
@ -276,11 +276,14 @@ if len(stateBytes) > 0 {
}
```
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.
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
@ -313,7 +316,7 @@ example-plugin key new > key.json
Here's what my `key.json looks like:
```
```json
{
"address": "15F591CA434CFCCBDEC1D206F3ED3EBA207BFE7D",
"priv_key": [
@ -329,7 +332,7 @@ Here's what my `key.json looks like:
Now we can make a `genesis.json` file and add an account with out public key:
```
```json
[
"base/chainID", "example-chain",
"base/account", {
@ -346,7 +349,7 @@ Now we can make a `genesis.json` file and add an account with out public key:
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:
Before we can start the blockchain, we must initialize and/or reset the Tendermint state for a new blockchain:
```
tendermint init
@ -410,7 +413,7 @@ 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 Stpes
## 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.

View File

@ -24,16 +24,16 @@ The purpose of IBC is to enable one blockchain to function as a light-client of
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.
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
in the block header called `AppHash`, which refers to the Merkle root hash of
the application after processing the transactions from the previous block. So,
if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit` at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1)
if we want to verify the `AppHash` from height H, we need the signatures from `LastCommit`
at height H+1. (And remember that this `AppHash` only contains the results from all transactions up to and including block H-1)
Unlike Proof-of-Work, the light-client protocol does not need to download and
check all the headers in the blockchain - the client can always jump straight
@ -46,15 +46,12 @@ 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
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.
@ -63,7 +60,7 @@ Each of these steps involves a separate IBC transaction type. Let's take them up
The `IBCRegisterChainTx` is used to register one chain on another.
It contains the chain ID and genesis configuration of the chain to register:
```
```golang
type IBCRegisterChainTx struct {
BlockchainGenesis
}
@ -82,7 +79,7 @@ This transaction should only be sent once for a given chain ID, and successive s
The `IBCUpdateChainTx` is used to update the state of one chain on another.
It contains the header and commit signatures for some block in the chain:
```
```golang
type IBCUpdateChainTx struct {
Header tm.Header
Commit tm.Commit
@ -96,11 +93,11 @@ Anyone can relay an `IBCUpdateChainTx`, and they only need to do so as frequentl
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.),
a sequence number (i.e. an integer that increments with every message sent between this pair of chains),
a packet type (e.g. coin, data, etc.),
and a payload.
```
```golang
type IBCPacketCreateTx struct {
Packet
}
@ -129,7 +126,7 @@ Those funds can only be unlocked with corresponding IBC messages back from
The `IBCPacketPostTx` is used to post an outgoing packet from one chain to another.
It contains the packet and a proof that the packet was committed into the state of the sending chain:
```
```golang
type IBCPacketPostTx struct {
FromChainID string // The immediate source of the packet, not always Packet.SrcChainID
FromChainHeight uint64 // The block height in which Packet was committed, to check Proof
@ -138,7 +135,7 @@ type IBCPacketPostTx struct {
}
```
The proof is a merkle proof in an IAVL tree, our implementation of a balanced, Merklized binary search tree.
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,
@ -147,14 +144,12 @@ 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.
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.
@ -180,8 +175,13 @@ The results of a query can thus be used as proof in an `IBCPacketPostTx`.
Now that we have all the background knowledge, let's actually walk through the tutorial.
Make sure you have installed
<<<<<<< 5be9db68dbd6a69ba886c5a6e55b90f2cecd2ca8
[tendermint](https://tendermint.com/intro/getting-started/download) and
[basecoin](/docs/guide/install.md).
=======
[Tendermint](https://tendermint.com/intro/getting-started/download) and
[adam](/docs/guide/install.md).
>>>>>>> doc edits
`basecoin` is a framework for creating new cryptocurrency applications.
@ -252,8 +252,8 @@ 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.
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.
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.
@ -288,13 +288,13 @@ Here, `<height + 1>` is one greater than the height retuned by the previous `que
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,
The most important part was that we updated chain2 with the latest state (i.e. header and commit) of chain1,
and then were able to post a proof to chain2 that a packet was committed to the outgoing state of chain1.
In a future tutorial, we will demonstrate how to use IBC to actually transfer tokens between two blockchains,
but we'll do it with real testnets deployed across multiple nodes on the network. Stay tuned!

View File

@ -9,7 +9,7 @@ Here we describe how that functionality can be achieved through a plugin system.
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"`
@ -20,7 +20,7 @@ type AppTx struct {
```
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,
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`.
@ -31,7 +31,7 @@ and some coins that can be forwarded to the plugin as well.
A plugin is simply a Go package that implements the `Plugin` interface:
```
```golang
type Plugin interface {
// Name of this plugin, should be short.
@ -61,7 +61,7 @@ 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.
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!
## Examples

View File

@ -8,17 +8,24 @@ import (
"github.com/tendermint/basecoin/types"
)
//Called during CLI initialization
func init() {
//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() })
}
var (
//CLI Flags
ExampleFlag = cli.BoolFlag{
Name: "valid",
Usage: "Set this to make the transaction valid",
}
//CLI Plugin Commands
ExamplePluginTxCmd = cli.Command{
Name: "example",
Usage: "Create, sign, and broadcast a transaction to the example plugin",
@ -29,8 +36,17 @@ var (
}
)
//Send a transaction
func cmdExamplePluginTx(c *cli.Context) error {
//Retrieve any flag results
exampleFlag := c.Bool("valid")
//Create a transaction object with flag results
exampleTx := ExamplePluginTx{exampleFlag}
return commands.AppTx(c, "example-plugin", wire.BinaryBytes(exampleTx))
//Encode transaction bytes
exampleTxBytes := wire.BinaryBytes(exampleTx)
//Send the transaction and return any errors
return commands.AppTx(c, "example-plugin", exampleTxBytes)
}

View File

@ -8,6 +8,7 @@ import (
)
func main() {
//Initialize an instance of basecoin with default basecoin commands
app := cli.NewApp()
app.Name = "example-plugin"
app.Usage = "example-plugin [command] [args...]"

View File

@ -6,46 +6,104 @@ import (
"github.com/tendermint/go-wire"
)
//-----------------------------------------
// Structs
// * Note the fields in each struct may be expanded/modified
// Plugin State Struct
// * Intended to store the current state of the plugin
// * This example contains a field which holds the execution count
// * Used by go-wire as the encoding/decoding struct to hold the plugin state
// * All fields must be exposed (for go-wire)
// * The state is stored within the KVStore using the key retrieved
// from the ExamplePlugin.StateKey() function
type ExamplePluginState struct {
Counter int
}
// Transaction Struct
// * Stores transaction-specific plugin-customized information
// * This example contains a dummy field 'Valid' intended to specify
// if the transaction is a valid and should proceed
// * Used by go-wire as the encoding/decoding struct to pass transaction
// * All fields must be exposed (for go-wire)
// * Passed through txBytes in the RunTx func.
type ExamplePluginTx struct {
Valid bool
}
// Plugin Struct
// * Struct which satisfies the basecoin Plugin interface
// * Stores global plugin settings, in this example just the plugin name
type ExamplePlugin struct {
name string
}
func (ep *ExamplePlugin) Name() string {
return ep.name
}
func (ep *ExamplePlugin) StateKey() []byte {
return []byte("ExamplePlugin.State")
}
//-----------------------------------------
// Non-Mandatory Functions
// Return a new example plugin pointer with a hard-coded name. Within other
// plugin implementations may choose to include other initialization
// information to populate custom fields of your Plugin struct in this example
// named ExamplePlugin
func NewExamplePlugin() *ExamplePlugin {
return &ExamplePlugin{
name: "example-plugin",
}
}
// Return a byte array unique to this plugin which will be used as the key which
// to store the plugin state (ExamplePluginState)
func (ep *ExamplePlugin) StateKey() []byte {
return []byte("ExamplePlugin.State")
}
//-----------------------------------------
// Basecoin Plugin Interface Functions
//Return the name of the plugin
func (ep *ExamplePlugin) Name() string {
return ep.name
}
// SetOption may be called during genesis of basecoin and can be used to set
// initial plugin parameters. Within genesis.json file entries are made in
// the format: "<plugin>/<key>", "<value>" Where <plugin> is the plugin name,
// in this file ExamplePlugin.name, and <key> and <value> are the strings passed
// into the plugin SetOption function. This function is intended to be used to
// set plugin specific information such as the plugin state. Within this example
// SetOption is left unimplemented.
func (ep *ExamplePlugin) SetOption(store types.KVStore, key string, value string) (log string) {
return ""
}
// The core tx logic of the app is containted within the RunTx function
// Input fields:
// - store types.KVStore
// - This term provides read/write capabilities to the merkelized data store
// which is accessible cross-plugin
// - ctx types.CallContext
// - The ctx contains the callers address, a pointer to the callers account,
// and an amount of coins sent with the transaction
// - txBytes []byte
// - Used to send customized information from the basecoin
// application to your plugin
//
// Other more complex plugins may have a variant on the process order within this
// example including loading and saving multiple or variable states, or not
// including a state stored in the KVStore whatsoever.
func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txBytes []byte) (res abci.Result) {
// Decode tx
// 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())
}
// Validate tx
// Perform Transaction Validation
if !tx.Valid {
return abci.ErrInternalError.AppendLog("Valid must be true")
}
@ -53,8 +111,10 @@ func (ep *ExamplePlugin) RunTx(store types.KVStore, ctx types.CallContext, txByt
// 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)
err = wire.ReadBinaryBytes(stateBytes, &pluginState) //decode using go-wire
if err != nil {
return abci.ErrInternalError.AppendLog("Error decoding state: " + err.Error())
}